Completed
Push — release-2.1 ( 98fee7...3b37ab )
by Jeremy
13s
created

ManageBans.php ➔ list_getBanItems()   D

Complexity

Conditions 13
Paths 206

Size

Total Lines 93

Duplication

Lines 10
Ratio 10.75 %

Importance

Changes 0
Metric Value
cc 13
nc 206
nop 4
dl 10
loc 93
rs 4.7192
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
 * @todo refactor as controller-model
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

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

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

Loading history...
6
 *
7
 * Simple Machines Forum (SMF)
8
 *
9
 * @package SMF
10
 * @author Simple Machines http://www.simplemachines.org
11
 * @copyright 2018 Simple Machines and individual contributors
12
 * @license http://www.simplemachines.org/about/smf/license.php BSD
13
 *
14
 * @version 2.1 Beta 4
15
 */
16
17
if (!defined('SMF'))
18
	die('No direct access...');
19
20
/**
21
 * Ban center. The main entrance point for all ban center functions.
22
 * It is accesssed by ?action=admin;area=ban.
23
 * It choses a function based on the 'sa' parameter, like many others.
24
 * The default sub-action is BanList().
25
 * It requires the ban_members permission.
26
 * It initializes the admin tabs.
27
 *
28
 * @uses ManageBans template.
29
 */
30
function Ban()
31
{
32
	global $context, $txt, $scripturl;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
33
34
	isAllowedTo('manage_bans');
35
36
	loadTemplate('ManageBans');
37
38
	$subActions = array(
39
		'add' => 'BanEdit',
40
		'browse' => 'BanBrowseTriggers',
41
		'edittrigger' => 'BanEditTrigger',
42
		'edit' => 'BanEdit',
43
		'list' => 'BanList',
44
		'log' => 'BanLog',
45
	);
46
47
	// Default the sub-action to 'view ban list'.
48
	$_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'list';
49
50
	$context['page_title'] = $txt['ban_title'];
51
	$context['sub_action'] = $_REQUEST['sa'];
52
53
	// Tabs for browsing the different ban functions.
54
	$context[$context['admin_menu_name']]['tab_data'] = array(
55
		'title' => $txt['ban_title'],
56
		'help' => 'ban_members',
57
		'description' => $txt['ban_description'],
58
		'tabs' => array(
59
			'list' => array(
60
				'description' => $txt['ban_description'],
61
				'href' => $scripturl . '?action=admin;area=ban;sa=list',
62
				'is_selected' => $_REQUEST['sa'] == 'list' || $_REQUEST['sa'] == 'edit' || $_REQUEST['sa'] == 'edittrigger',
63
			),
64
			'add' => array(
65
				'description' => $txt['ban_description'],
66
				'href' => $scripturl . '?action=admin;area=ban;sa=add',
67
				'is_selected' => $_REQUEST['sa'] == 'add',
68
			),
69
			'browse' => array(
70
				'description' => $txt['ban_trigger_browse_description'],
71
				'href' => $scripturl . '?action=admin;area=ban;sa=browse',
72
				'is_selected' => $_REQUEST['sa'] == 'browse',
73
			),
74
			'log' => array(
75
				'description' => $txt['ban_log_description'],
76
				'href' => $scripturl . '?action=admin;area=ban;sa=log',
77
				'is_selected' => $_REQUEST['sa'] == 'log',
78
				'is_last' => true,
79
			),
80
		),
81
	);
82
83
	call_integration_hook('integrate_manage_bans', array(&$subActions));
84
85
	// Call the right function for this sub-action.
86
	call_helper($subActions[$_REQUEST['sa']]);
87
}
88
89
/**
90
 * Shows a list of bans currently set.
91
 * It is accessed by ?action=admin;area=ban;sa=list.
92
 * It removes expired bans.
93
 * It allows sorting on different criteria.
94
 * It also handles removal of selected ban items.
95
 *
96
 * @uses the main ManageBans template.
97
 */
98
function BanList()
99
{
100
	global $txt, $context, $scripturl;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
101
	global $user_info, $sourcedir, $modSettings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
102
103
	// User pressed the 'remove selection button'.
104
	if (!empty($_POST['removeBans']) && !empty($_POST['remove']) && is_array($_POST['remove']))
105
	{
106
		checkSession();
107
108
		// Make sure every entry is a proper integer.
109
		array_map('intval', $_POST['remove']);
110
111
		// Unban them all!
112
		removeBanGroups($_POST['remove']);
113
		removeBanTriggers($_POST['remove']);
114
115
		// No more caching this ban!
116
		updateSettings(array('banLastUpdated' => time()));
117
118
		// Some members might be unbanned now. Update the members table.
119
		updateBanMembers();
120
	}
121
122
	// Create a date string so we don't overload them with date info.
123
	if (preg_match('~%[AaBbCcDdeGghjmuYy](?:[^%]*%[AaBbCcDdeGghjmuYy])*~', $user_info['time_format'], $matches) == 0 || empty($matches[0]))
124
		$context['ban_time_format'] = $user_info['time_format'];
125
	else
126
		$context['ban_time_format'] = $matches[0];
127
128
	$listOptions = array(
129
		'id' => 'ban_list',
130
		'title' => $txt['ban_title'],
131
		'items_per_page' => $modSettings['defaultMaxListItems'],
132
		'base_href' => $scripturl . '?action=admin;area=ban;sa=list',
133
		'default_sort_col' => 'added',
134
		'default_sort_dir' => 'desc',
135
		'get_items' => array(
136
			'function' => 'list_getBans',
137
		),
138
		'get_count' => array(
139
			'function' => 'list_getNumBans',
140
		),
141
		'no_items_label' => $txt['ban_no_entries'],
142
		'columns' => array(
143
			'name' => array(
144
				'header' => array(
145
					'value' => $txt['ban_name'],
146
				),
147
				'data' => array(
148
					'db' => 'name',
149
				),
150
				'sort' => array(
151
					'default' => 'bg.name',
152
					'reverse' => 'bg.name DESC',
153
				),
154
			),
155
			'notes' => array(
156
				'header' => array(
157
					'value' => $txt['ban_notes'],
158
				),
159
				'data' => array(
160
					'db' => 'notes',
161
					'class' => 'smalltext',
162
				),
163
				'sort' => array(
164
					'default' => 'LENGTH(bg.notes) > 0 DESC, bg.notes',
165
					'reverse' => 'LENGTH(bg.notes) > 0, bg.notes DESC',
166
				),
167
			),
168
			'reason' => array(
169
				'header' => array(
170
					'value' => $txt['ban_reason'],
171
				),
172
				'data' => array(
173
					'db' => 'reason',
174
					'class' => 'smalltext',
175
				),
176
				'sort' => array(
177
					'default' => 'LENGTH(bg.reason) > 0 DESC, bg.reason',
178
					'reverse' => 'LENGTH(bg.reason) > 0, bg.reason DESC',
179
				),
180
			),
181
			'added' => array(
182
				'header' => array(
183
					'value' => $txt['ban_added'],
184
				),
185
				'data' => array(
186
					'function' => function($rowData) use ($context)
187
					{
188
						return timeformat($rowData['ban_time'], empty($context['ban_time_format']) ? true : $context['ban_time_format']);
189
					},
190
				),
191
				'sort' => array(
192
					'default' => 'bg.ban_time',
193
					'reverse' => 'bg.ban_time DESC',
194
				),
195
			),
196
			'expires' => array(
197
				'header' => array(
198
					'value' => $txt['ban_expires'],
199
				),
200
				'data' => array(
201
					'function' => function($rowData) use ($txt)
202
					{
203
						// This ban never expires...whahaha.
204
						if ($rowData['expire_time'] === null)
205
							return $txt['never'];
206
207
						// This ban has already expired.
208
						elseif ($rowData['expire_time'] < time())
209
							return sprintf('<span class="red">%1$s</span>', $txt['ban_expired']);
210
211
						// Still need to wait a few days for this ban to expire.
212
						else
213
							return sprintf('%1$d&nbsp;%2$s', ceil(($rowData['expire_time'] - time()) / (60 * 60 * 24)), $txt['ban_days']);
214
					},
215
				),
216
				'sort' => array(
217
					'default' => 'COALESCE(bg.expire_time, 1=1) DESC, bg.expire_time DESC',
218
					'reverse' => 'COALESCE(bg.expire_time, 1=1), bg.expire_time',
219
				),
220
			),
221
			'num_triggers' => array(
222
				'header' => array(
223
					'value' => $txt['ban_triggers'],
224
				),
225
				'data' => array(
226
					'db' => 'num_triggers',
227
				),
228
				'sort' => array(
229
					'default' => 'num_triggers DESC',
230
					'reverse' => 'num_triggers',
231
				),
232
			),
233
			'actions' => array(
234
				'header' => array(
235
					'value' => $txt['ban_actions'],
236
					'class' => 'centercol',
237
				),
238
				'data' => array(
239
					'sprintf' => array(
240
						'format' => '<a href="' . $scripturl . '?action=admin;area=ban;sa=edit;bg=%1$d">' . $txt['modify'] . '</a>',
241
						'params' => array(
242
							'id_ban_group' => false,
243
						),
244
					),
245
					'class' => 'centercol',
246
				),
247
			),
248
			'check' => array(
249
				'header' => array(
250
					'value' => '<input type="checkbox" onclick="invertAll(this, this.form);">',
251
					'class' => 'centercol',
252
				),
253
				'data' => array(
254
					'sprintf' => array(
255
						'format' => '<input type="checkbox" name="remove[]" value="%1$d">',
256
						'params' => array(
257
							'id_ban_group' => false,
258
						),
259
					),
260
					'class' => 'centercol',
261
				),
262
			),
263
		),
264
		'form' => array(
265
			'href' => $scripturl . '?action=admin;area=ban;sa=list',
266
		),
267
		'additional_rows' => array(
268
			array(
269
				'position' => 'top_of_list',
270
				'value' => '<input type="submit" name="removeBans" value="' . $txt['ban_remove_selected'] . '" class="button">',
271
			),
272
			array(
273
				'position' => 'bottom_of_list',
274
				'value' => '<input type="submit" name="removeBans" value="' . $txt['ban_remove_selected'] . '" class="button">',
275
			),
276
		),
277
		'javascript' => '
278
		var removeBans = $("input[name=\'removeBans\']");
279
280
		removeBans.on( "click", function(e) {
281
			var removeItems = $("input[name=\'remove[]\']:checked").length;
282
283
			if (removeItems == 0)
284
			{
285
				e.preventDefault();
286
				return alert("'. $txt['select_item_check'] .'");
287
			}
288
289
290
			return confirm("'. $txt['ban_remove_selected_confirm'] .'");
291
		});',
292
	);
293
294
	require_once($sourcedir . '/Subs-List.php');
295
	createList($listOptions);
296
297
	$context['sub_template'] = 'show_list';
298
	$context['default_list'] = 'ban_list';
299
}
300
301
/**
302
 * Get bans, what else? For the given options.
303
 *
304
 * @param int $start Which item to start with (for pagination purposes)
305
 * @param int $items_per_page How many items to show on each page
306
 * @param string $sort A string telling ORDER BY how to sort the results
307
 * @return array An array of information about the bans for the list
308
 */
309 View Code Duplication
function list_getBans($start, $items_per_page, $sort)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

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

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

Loading history...
310
{
311
	global $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
312
313
	$request = $smcFunc['db_query']('', '
314
		SELECT bg.id_ban_group, bg.name, bg.ban_time, bg.expire_time, bg.reason, bg.notes, COUNT(bi.id_ban) AS num_triggers
315
		FROM {db_prefix}ban_groups AS bg
316
			LEFT JOIN {db_prefix}ban_items AS bi ON (bi.id_ban_group = bg.id_ban_group)
317
		GROUP BY bg.id_ban_group, bg.name, bg.ban_time, bg.expire_time, bg.reason, bg.notes
318
		ORDER BY {raw:sort}
319
		LIMIT {int:offset}, {int:limit}',
320
		array(
321
			'sort' => $sort,
322
			'offset' => $start,
323
			'limit' => $items_per_page,
324
		)
325
	);
326
	$bans = array();
327
	while ($row = $smcFunc['db_fetch_assoc']($request))
328
		$bans[] = $row;
329
330
	$smcFunc['db_free_result']($request);
331
332
	return $bans;
333
}
334
335
/**
336
 * Get the total number of ban from the ban group table
337
 *
338
 * @return int The total number of bans
339
 */
340
function list_getNumBans()
341
{
342
	global $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
343
344
	$request = $smcFunc['db_query']('', '
345
		SELECT COUNT(*) AS num_bans
346
		FROM {db_prefix}ban_groups',
347
		array(
348
		)
349
	);
350
	list ($numBans) = $smcFunc['db_fetch_row']($request);
351
	$smcFunc['db_free_result']($request);
352
353
	return $numBans;
354
}
355
356
/**
357
 * This function is behind the screen for adding new bans and modifying existing ones.
358
 * Adding new bans:
359
 * 	- is accessed by ?action=admin;area=ban;sa=add.
360
 * 	- uses the ban_edit sub template of the ManageBans template.
361
 * Modifying existing bans:
362
 *  - is accessed by ?action=admin;area=ban;sa=edit;bg=x
363
 *  - uses the ban_edit sub template of the ManageBans template.
364
 *  - shows a list of ban triggers for the specified ban.
365
 */
366
function BanEdit()
367
{
368
	global $txt, $modSettings, $context, $scripturl, $smcFunc, $sourcedir;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
369
370
	if ((isset($_POST['add_ban']) || isset($_POST['modify_ban']) || isset($_POST['remove_selection'])) && empty($context['ban_errors']))
371
		BanEdit2();
372
373
	$ban_group_id = isset($context['ban']['id']) ? $context['ban']['id'] : (isset($_REQUEST['bg']) ? (int) $_REQUEST['bg'] : 0);
374
375
	// Template needs this to show errors using javascript
376
	loadLanguage('Errors');
377
	createToken('admin-bet');
378
	$context['form_url'] = $scripturl . '?action=admin;area=ban;sa=edit';
379
380
	if (!empty($context['ban_errors']))
381
		foreach ($context['ban_errors'] as $error)
0 ignored issues
show
Bug introduced by
The expression $context['ban_errors'] of type string is not traversable.
Loading history...
382
			$context['error_messages'][$error] = $txt[$error];
383
384
	else
385
	{
386
		// If we're editing an existing ban, get it from the database.
387
		if (!empty($ban_group_id))
388
		{
389
			$context['ban_group_id'] = $ban_group_id;
390
391
			// We're going to want this for making our list.
392
			require_once($sourcedir . '/Subs-List.php');
393
394
			$listOptions = array(
395
				'id' => 'ban_items',
396
				'base_href' => $scripturl . '?action=admin;area=ban;sa=edit;bg=' . $ban_group_id,
397
				'no_items_label' => $txt['ban_no_triggers'],
398
				'items_per_page' => $modSettings['defaultMaxListItems'],
399
				'get_items' => array(
400
					'function' => 'list_getBanItems',
401
					'params' => array(
402
						'ban_group_id' => $ban_group_id,
403
					),
404
				),
405
				'get_count' => array(
406
					'function' => 'list_getNumBanItems',
407
					'params' => array(
408
						'ban_group_id' => $ban_group_id,
409
					),
410
				),
411
				'columns' => array(
412
					'type' => array(
413
						'header' => array(
414
							'value' => $txt['ban_banned_entity'],
415
							'style' => 'width: 60%;text-align: left;',
416
						),
417
						'data' => array(
418
							'function' => function($ban_item) use ($txt)
419
							{
420
								if (in_array($ban_item['type'], array('ip', 'hostname', 'email')))
421
									return '<strong>' . $txt[$ban_item['type']] . ':</strong>&nbsp;' . $ban_item[$ban_item['type']];
422
								elseif ($ban_item['type'] == 'user')
423
									return '<strong>' . $txt['username'] . ':</strong>&nbsp;' . $ban_item['user']['link'];
424
								else
425
									return '<strong>' . $txt['unknown'] . ':</strong>&nbsp;' . $ban_item['no_bantype_selected'];
426
							},
427
							'style' => 'text-align: left;',
428
						),
429
					),
430
					'hits' => array(
431
						'header' => array(
432
							'value' => $txt['ban_hits'],
433
							'style' => 'width: 15%; text-align: center;',
434
						),
435
						'data' => array(
436
							'db' => 'hits',
437
							'style' => 'text-align: center;',
438
						),
439
					),
440
					'id' => array(
441
						'header' => array(
442
							'value' => $txt['ban_actions'],
443
							'style' => 'width: 15%; text-align: center;',
444
						),
445
						'data' => array(
446
							'function' => function($ban_item) use ($txt, $context, $scripturl)
447
							{
448
								return '<a href="' . $scripturl . '?action=admin;area=ban;sa=edittrigger;bg=' . $context['ban_group_id'] . ';bi=' . $ban_item['id'] . '">' . $txt['ban_edit_trigger'] . '</a>';
449
							},
450
							'style' => 'text-align: center;',
451
						),
452
					),
453
					'checkboxes' => array(
454
						'header' => array(
455
							'value' => '<input type="checkbox" onclick="invertAll(this, this.form, \'ban_items\');">',
456
							'style' => 'width: 5%; text-align: center;',
457
						),
458
						'data' => array(
459
							'sprintf' => array(
460
								'format' => '<input type="checkbox" name="ban_items[]" value="%1$d">',
461
								'params' => array(
462
									'id' => false,
463
								),
464
							),
465
							'style' => 'text-align: center;',
466
						),
467
					),
468
				),
469
				'form' => array(
470
					'href' => $scripturl . '?action=admin;area=ban;sa=edit;bg=' . $ban_group_id,
471
				),
472
				'additional_rows' => array(
473
					array(
474
						'position' => 'above_table_headers',
475
						'value' => '
476
						<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>',
477
						'style' => 'text-align: right;',
478
					),
479
					array(
480
						'position' => 'above_table_headers',
481
						'value' => '
482
						<input type="hidden" name="bg" value="' . $ban_group_id . '">
483
						<input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '">
484
						<input type="hidden" name="' . $context['admin-bet_token_var'] . '" value="' . $context['admin-bet_token'] . '">',
485
					),
486
					array(
487
						'position' => 'below_table_data',
488
						'value' => '
489
						<input type="submit" name="remove_selection" value="' . $txt['ban_remove_selected_triggers'] . '" class="button"> <a class="button" 
490
						href="' .
491
							$scripturl	. '?action=admin;area=ban;sa=edittrigger;bg=' . $ban_group_id . '">' . $txt['ban_add_trigger'] . '</a>',
492
						'style' => 'text-align: right;',
493
					),
494
					array(
495
						'position' => 'below_table_data',
496
						'value' => '
497
						<input type="hidden" name="bg" value="' . $ban_group_id . '">
498
						<input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '">
499
						<input type="hidden" name="' . $context['admin-bet_token_var'] . '" value="' . $context['admin-bet_token'] . '">',
500
					),
501
				),
502
				'javascript' => '
503
		var removeBans = $("input[name=\'remove_selection\']");
504
505
		removeBans.on( "click", function(e) {
506
			var removeItems = $("input[name=\'ban_items[]\']:checked").length;
507
508
			if (removeItems == 0)
509
			{
510
				e.preventDefault();
511
				return alert("'. $txt['select_item_check'] .'");
512
			}
513
514
515
			return confirm("'. $txt['ban_remove_selected_confirm'] .'");
516
		});',
517
			);
518
			createList($listOptions);
519
		}
520
		// Not an existing one, then it's probably a new one.
521
		else
522
		{
523
			$context['ban'] = array(
524
				'id' => 0,
525
				'name' => '',
526
				'expiration' => array(
527
					'status' => 'never',
528
					'days' => 0
529
				),
530
				'reason' => '',
531
				'notes' => '',
532
				'ban_days' => 0,
533
				'cannot' => array(
534
					'access' => true,
535
					'post' => false,
536
					'register' => false,
537
					'login' => false,
538
				),
539
				'is_new' => true,
540
			);
541
			$context['ban_suggestions'] = array(
542
				'main_ip' => '',
543
				'hostname' => '',
544
				'email' => '',
545
				'member' => array(
546
					'id' => 0,
547
				),
548
			);
549
550
			// Overwrite some of the default form values if a user ID was given.
551
			if (!empty($_REQUEST['u']))
552
			{
553
				$request = $smcFunc['db_query']('', '
554
					SELECT id_member, real_name, member_ip, email_address
555
					FROM {db_prefix}members
556
					WHERE id_member = {int:current_user}
557
					LIMIT 1',
558
					array(
559
						'current_user' => (int) $_REQUEST['u'],
560
					)
561
				);
562
				if ($smcFunc['db_num_rows']($request) > 0)
563
				{
564
					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);
565
					$context['ban_suggestions']['main_ip'] = inet_dtop($context['ban_suggestions']['main_ip']);
566
				}
567
				$smcFunc['db_free_result']($request);
568
569
				if (!empty($context['ban_suggestions']['member']['id']))
570
				{
571
					$context['ban_suggestions']['href'] = $scripturl . '?action=profile;u=' . $context['ban_suggestions']['member']['id'];
572
					$context['ban_suggestions']['member']['link'] = '<a href="' . $context['ban_suggestions']['href'] . '">' . $context['ban_suggestions']['member']['name'] . '</a>';
573
574
					// Default the ban name to the name of the banned member.
575
					$context['ban']['name'] = $context['ban_suggestions']['member']['name'];
576
					// @todo: there should be a better solution...used to lock the "Ban on Username" input when banning from profile
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
577
					$context['ban']['from_user'] = true;
578
579
					// Would be nice if we could also ban the hostname.
580
					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']))
581
						$context['ban_suggestions']['hostname'] = host_from_ip($context['ban_suggestions']['main_ip']);
582
583
					$context['ban_suggestions']['other_ips'] = banLoadAdditionalIPs($context['ban_suggestions']['member']['id']);
584
				}
585
			}
586
587
			// We come from the mod center.
588
			elseif (isset($_GET['msg']) && !empty($_GET['msg']))
589
			{
590
				$request = $smcFunc['db_query']('', '
591
					SELECT poster_name, poster_ip, poster_email
592
					FROM {db_prefix}messages
593
					WHERE id_msg = {int:message}
594
					LIMIT 1',
595
					array(
596
						'message' => (int) $_REQUEST['msg'],
597
					)
598
				);
599
				if ($smcFunc['db_num_rows']($request) > 0)
600
				{
601
					list ($context['ban_suggestions']['member']['name'], $context['ban_suggestions']['main_ip'], $context['ban_suggestions']['email']) = $smcFunc['db_fetch_row']($request);
602
					$context['ban_suggestions']['main_ip'] = inet_dtop($context['ban_suggestions']['main_ip']);
603
				}
604
				$smcFunc['db_free_result']($request);
605
606
				// Can't hurt to ban base on the guest name...
607
				$context['ban']['name'] = $context['ban_suggestions']['member']['name'];
608
				$context['ban']['from_user'] = true;
609
			}
610
		}
611
	}
612
613
	loadJavaScriptFile('suggest.js', array('minimize' => true), 'smf_suggest');
614
	$context['sub_template'] = 'ban_edit';
615
616
}
617
618
/**
619
 * Retrieves all the ban items belonging to a certain ban group
620
 *
621
 * @param int $start Which item to start with (for pagination purposes)
622
 * @param int $items_per_page How many items to show on each page
623
 * @param int $sort Not used here
624
 * @param int $ban_group_id The ID of the group to get the bans for
625
 * @return array An array with information about the returned ban items
626
 */
627
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...
628
{
629
	global $context, $smcFunc, $scripturl;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
630
631
	$ban_items = array();
632
	$request = $smcFunc['db_query']('', '
633
		SELECT
634
			bi.id_ban, bi.hostname, bi.email_address, bi.id_member, bi.hits,
635
			bi.ip_low, bi.ip_high,
636
			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,
637
			COALESCE(mem.id_member, 0) AS id_member, mem.member_name, mem.real_name
638
		FROM {db_prefix}ban_groups AS bg
639
			LEFT JOIN {db_prefix}ban_items AS bi ON (bi.id_ban_group = bg.id_ban_group)
640
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = bi.id_member)
641
		WHERE bg.id_ban_group = {int:current_ban}
642
		LIMIT {int:start}, {int:items_per_page}',
643
		array(
644
			'current_ban' => $ban_group_id,
645
			'start' => $start,
646
			'items_per_page' => $items_per_page,
647
		)
648
	);
649
	if ($smcFunc['db_num_rows']($request) == 0)
650
		fatal_lang_error('ban_not_found', false);
651
652
	while ($row = $smcFunc['db_fetch_assoc']($request))
653
	{
654
		if (!isset($context['ban']))
655
		{
656
			$context['ban'] = array(
657
				'id' => $row['id_ban_group'],
658
				'name' => $row['name'],
659
				'expiration' => array(
660
					'status' => empty($row['expire_time']) ? 'never' : ($row['expire_time'] < time() ? 'expired' : 'one_day'),
661
					'days' => $row['expire_time'] > time() ? ($row['expire_time'] - time() < 86400 ? 1 : ceil(($row['expire_time'] - time()) / 86400)) : 0
662
				),
663
				'reason' => $row['reason'],
664
				'notes' => $row['notes'],
665
				'cannot' => array(
666
					'access' => !empty($row['cannot_access']),
667
					'post' => !empty($row['cannot_post']),
668
					'register' => !empty($row['cannot_register']),
669
					'login' => !empty($row['cannot_login']),
670
				),
671
				'is_new' => false,
672
				'hostname' => '',
673
				'email' => '',
674
			);
675
		}
676
677
		if (!empty($row['id_ban']))
678
		{
679
			$ban_items[$row['id_ban']] = array(
680
				'id' => $row['id_ban'],
681
				'hits' => $row['hits'],
682
			);
683
			if (!empty($row['ip_high']))
684
			{
685
				$ban_items[$row['id_ban']]['type'] = 'ip';
686
				$ban_items[$row['id_ban']]['ip'] = range2ip($row['ip_low'], $row['ip_high']);
687
			}
688 View Code Duplication
			elseif (!empty($row['hostname']))
689
			{
690
				$ban_items[$row['id_ban']]['type'] = 'hostname';
691
				$ban_items[$row['id_ban']]['hostname'] = str_replace('%', '*', $row['hostname']);
692
			}
693 View Code Duplication
			elseif (!empty($row['email_address']))
694
			{
695
				$ban_items[$row['id_ban']]['type'] = 'email';
696
				$ban_items[$row['id_ban']]['email'] = str_replace('%', '*', $row['email_address']);
697
			}
698
			elseif (!empty($row['id_member']))
699
			{
700
				$ban_items[$row['id_ban']]['type'] = 'user';
701
				$ban_items[$row['id_ban']]['user'] = array(
702
					'id' => $row['id_member'],
703
					'name' => $row['real_name'],
704
					'href' => $scripturl . '?action=profile;u=' . $row['id_member'],
705
					'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>',
706
				);
707
			}
708
			// Invalid ban (member probably doesn't exist anymore).
709
			else
710
			{
711
				unset($ban_items[$row['id_ban']]);
712
				removeBanTriggers($row['id_ban']);
713
			}
714
		}
715
	}
716
	$smcFunc['db_free_result']($request);
717
718
	return $ban_items;
719
}
720
721
/**
722
 * Gets the number of ban items belonging to a certain ban group
723
 *
724
 * @return int The number of ban items
725
 */
726
function list_getNumBanItems()
727
{
728
	global $smcFunc, $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
729
730
	$ban_group_id = isset($context['ban_group_id']) ? $context['ban_group_id'] : 0;
731
732
	$request = $smcFunc['db_query']('', '
733
		SELECT COUNT(bi.id_ban)
734
		FROM {db_prefix}ban_groups AS bg
735
			LEFT JOIN {db_prefix}ban_items AS bi ON (bi.id_ban_group = bg.id_ban_group)
736
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = bi.id_member)
737
		WHERE bg.id_ban_group = {int:current_ban}',
738
		array(
739
			'current_ban' => $ban_group_id,
740
		)
741
	);
742
	list($banNumber) = $smcFunc['db_fetch_row']($request);
743
	$smcFunc['db_free_result']($request);
744
745
	return $banNumber;
746
}
747
748
/**
749
 * Finds additional IPs related to a certain user
750
 *
751
 * @param int $member_id The ID of the member to get additional IPs for
752
 * @return array An containing two arrays - ips_in_messages (IPs used in posts) and ips_in_errors (IPs used in error messages)
753
 */
754
function banLoadAdditionalIPs($member_id)
755
{
756
	// Borrowing a few language strings from profile.
757
	loadLanguage('Profile');
758
759
	$search_list = array();
760
	call_integration_hook('integrate_load_addtional_ip_ban', array(&$search_list));
761
	$search_list += array('ips_in_messages' => 'banLoadAdditionalIPsMember', 'ips_in_errors' => 'banLoadAdditionalIPsError');
762
763
	$return = array();
764
	foreach ($search_list as $key => $callable)
765
		if (is_callable($callable))
766
			$return[$key] = call_user_func($callable, $member_id);
767
768
	return $return;
769
}
770
771
/**
772
 * @param int $member_id The ID of the member
773
 * @return array An array of IPs used in posts by this member
774
 */
775 View Code Duplication
function banLoadAdditionalIPsMember($member_id)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

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

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

Loading history...
776
{
777
	global $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
778
779
	// Find some additional IP's used by this member.
780
	$message_ips = array();
781
	$request = $smcFunc['db_query']('', '
782
		SELECT DISTINCT poster_ip
783
		FROM {db_prefix}messages
784
		WHERE id_member = {int:current_user}
785
			AND poster_ip IS NOT NULL
786
		ORDER BY poster_ip',
787
		array(
788
			'current_user' => $member_id,
789
		)
790
	);
791
	while ($row = $smcFunc['db_fetch_assoc']($request))
792
		$message_ips[] = inet_dtop($row['poster_ip']);
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 View Code Duplication
function banLoadAdditionalIPsError($member_id)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

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

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

Loading history...
803
{
804
	global $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
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
	$smcFunc['db_free_result']($request);
820
821
	return $error_ips;
822
}
823
824
/**
825
 * This function handles submitted forms that add, modify or remove ban triggers.
826
 */
827
function banEdit2()
828
{
829
	global $smcFunc, $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
830
831
	checkSession();
832
	validateToken('admin-bet');
833
834
	$context['ban_errors'] = array();
835
836
	// Adding or editing a ban group
837
	if (isset($_POST['add_ban']) || isset($_POST['modify_ban']))
838
	{
839
		// Let's collect all the information we need
840
		$ban_info['id'] = isset($_REQUEST['bg']) ? (int) $_REQUEST['bg'] : 0;
0 ignored issues
show
Coding Style Comprehensibility 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.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
841
		$ban_info['is_new'] = empty($ban_info['id']);
842
		$ban_info['expire_date'] = !empty($_POST['expire_date']) ? (int) $_POST['expire_date'] : 0;
843
		$ban_info['expiration'] = array(
844
			'status' => isset($_POST['expiration']) && in_array($_POST['expiration'], array('never', 'one_day', 'expired')) ? $_POST['expiration'] : 'never',
845
			'days' => $ban_info['expire_date'],
846
		);
847
		$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);
848
		$ban_info['full_ban'] = empty($_POST['full_ban']) ? 0 : 1;
849
		$ban_info['reason'] = !empty($_POST['reason']) ? $smcFunc['htmlspecialchars']($_POST['reason'], ENT_QUOTES) : '';
850
		$ban_info['name'] = !empty($_POST['ban_name']) ? $smcFunc['htmlspecialchars']($_POST['ban_name'], ENT_QUOTES) : '';
851
		$ban_info['notes'] = isset($_POST['notes']) ? $smcFunc['htmlspecialchars']($_POST['notes'], ENT_QUOTES) : '';
852
		$ban_info['notes'] = str_replace(array("\r", "\n", '  '), array('', '<br>', '&nbsp; '), $ban_info['notes']);
853
		$ban_info['cannot']['access'] = empty($ban_info['full_ban']) ? 0 : 1;
854
		$ban_info['cannot']['post'] = !empty($ban_info['full_ban']) || empty($_POST['cannot_post']) ? 0 : 1;
855
		$ban_info['cannot']['register'] = !empty($ban_info['full_ban']) || empty($_POST['cannot_register']) ? 0 : 1;
856
		$ban_info['cannot']['login'] = !empty($ban_info['full_ban']) || empty($_POST['cannot_login']) ? 0 : 1;
857
858
		// Adding a new ban group
859
		if (empty($_REQUEST['bg']))
860
			$ban_group_id = insertBanGroup($ban_info);
861
		// Editing an existing ban group
862
		else
863
			$ban_group_id = updateBanGroup($ban_info);
864
865
		if (is_numeric($ban_group_id))
866
		{
867
			$ban_info['id'] = $ban_group_id;
868
			$ban_info['is_new'] = false;
869
		}
870
871
		$context['ban'] = $ban_info;
872
	}
873
874
	if (isset($_POST['ban_suggestions']))
875
		// @TODO: is $_REQUEST['bi'] ever set?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
876
		$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
Bug introduced by
The variable $ban_info does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
877
878
	// Something went wrong somewhere... Oh well, let's go back.
879
	if (!empty($context['ban_errors']))
880
	{
881
		$context['ban_suggestions'] = !empty($saved_triggers) ? $saved_triggers : array();
882
		$context['ban']['from_user'] = true;
883
		$context['ban_suggestions'] = array_merge($context['ban_suggestions'], getMemberData((int) $_REQUEST['u']));
884
885
		// Not strictly necessary, but it's nice
886
		if (!empty($context['ban_suggestions']['member']['id']))
887
			$context['ban_suggestions']['other_ips'] = banLoadAdditionalIPs($context['ban_suggestions']['member']['id']);
888
		return BanEdit();
889
	}
890
	$context['ban_suggestions']['saved_triggers'] = !empty($saved_triggers) ? $saved_triggers : array();
891
892
	if (isset($_POST['ban_items']))
893
	{
894
		$ban_group_id = isset($_REQUEST['bg']) ? (int) $_REQUEST['bg'] : 0;
895
		array_map('intval', $_POST['ban_items']);
896
897
		removeBanTriggers($_POST['ban_items'], $ban_group_id);
898
	}
899
900
	// Register the last modified date.
901
	updateSettings(array('banLastUpdated' => time()));
902
903
	// Update the member table to represent the new ban situation.
904
	updateBanMembers();
905
	redirectexit('action=admin;area=ban;sa=edit;bg=' . $ban_group_id);
0 ignored issues
show
Bug introduced by
The variable $ban_group_id does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
906
}
907
908
/**
909
 * Saves one or more ban triggers into a ban item: according to the suggestions
910
 * checks the $_POST variable to verify if the trigger is present
911
 *
912
 * @param array $suggestions An array of suggestedtriggers (IP, email, etc.)
913
 * @param int $ban_group The ID of the group we're saving bans for
914
 * @param int $member The ID of the member associated with this ban (if applicable)
915
 * @param int $ban_id The ID of the ban (0 if this is a new ban)
916
 *
917
 * @return array|bool An array with the triggers if there were errors or false on success
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array|false.

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

Loading history...
918
 */
919
function saveTriggers($suggestions = array(), $ban_group, $member = 0, $ban_id = 0)
0 ignored issues
show
Coding Style introduced by
Parameters which have default values should be placed at the end.

If you place a parameter with a default value before a parameter with a default value, the default value of the first parameter will never be used as it will always need to be passed anyway:

// $a must always be passed; it's default value is never used.
function someFunction($a = 5, $b) { }
Loading history...
920
{
921
	global $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
922
923
	$triggers = array(
924
		'main_ip' => '',
925
		'hostname' => '',
926
		'email' => '',
927
		'member' => array(
928
			'id' => $member,
929
		)
930
	);
931
932
	foreach ($suggestions as $key => $value)
933
	{
934
		if (is_array($value))
935
			$triggers[$key] = $value;
936
		else
937
			$triggers[$value] = !empty($_POST[$value]) ? $_POST[$value] : '';
938
	}
939
940
	$ban_triggers = validateTriggers($triggers);
941
942
	// Time to save!
943
	if (!empty($ban_triggers['ban_triggers']) && empty($context['ban_errors']))
944
	{
945
		if (empty($ban_id))
946
			addTriggers($ban_group, $ban_triggers['ban_triggers'], $ban_triggers['log_info']);
947
		else
948
			updateTriggers($ban_id, $ban_group, array_shift($ban_triggers['ban_triggers']), $ban_triggers['log_info']);
949
	}
950
	if (!empty($context['ban_errors']))
951
		return $triggers;
952
	else
953
		return false;
954
}
955
956
/**
957
 * This function removes a bunch of triggers based on ids
958
 * Doesn't clean the inputs
959
 *
960
 * @param array $items_ids The items to remove
961
 * @param bool|int $group_id The ID of the group these triggers are associated with or false if deleting them from all groups
962
 * @return bool Always returns true
963
 */
964
function removeBanTriggers($items_ids = array(), $group_id = false)
965
{
966
	global $smcFunc, $scripturl;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
967
968
	if ($group_id !== false)
969
		$group_id = (int) $group_id;
970
971
	if (empty($group_id) && empty($items_ids))
972
		return false;
973
974
	if (!is_array($items_ids))
975
		$items_ids = array($items_ids);
976
977
	$log_info = array();
978
	$ban_items = array();
979
980
	// First order of business: Load up the info so we can log this...
981
	$request = $smcFunc['db_query']('', '
982
		SELECT
983
			bi.id_ban, bi.hostname, bi.email_address, bi.id_member, bi.hits,
984
			bi.ip_low, bi.ip_high,
985
			COALESCE(mem.id_member, 0) AS id_member, mem.member_name, mem.real_name
986
		FROM {db_prefix}ban_items AS bi
987
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = bi.id_member)
988
		WHERE bi.id_ban IN ({array_int:ban_list})',
989
		array(
990
			'ban_list' => $items_ids,
991
		)
992
	);
993
994
	// Get all the info for the log
995
	while ($row = $smcFunc['db_fetch_assoc']($request))
996
	{
997
		if (!empty($row['id_ban']))
998
		{
999
			$ban_items[$row['id_ban']] = array(
1000
				'id' => $row['id_ban'],
1001
			);
1002
			if (!empty($row['ip_high']))
1003
			{
1004
				$ban_items[$row['id_ban']]['type'] = 'ip';
1005
				$ban_items[$row['id_ban']]['ip'] = range2ip($row['ip_low'], $row['ip_high']);
1006
1007
				$is_range = (strpos($ban_items[$row['id_ban']]['ip'], '-') !== false || strpos($ban_items[$row['id_ban']]['ip'], '*') !== false);
1008
1009
				$log_info[] = array(
1010
					'bantype' => ($is_range ? 'ip_range' : 'main_ip'),
1011
					'value' => $ban_items[$row['id_ban']]['ip'],
1012
				);
1013
			}
1014 View Code Duplication
			elseif (!empty($row['hostname']))
1015
			{
1016
				$ban_items[$row['id_ban']]['type'] = 'hostname';
1017
				$ban_items[$row['id_ban']]['hostname'] = str_replace('%', '*', $row['hostname']);
1018
				$log_info[] = array(
1019
					'bantype' => 'hostname',
1020
					'value' => $row['hostname'],
1021
				);
1022
			}
1023 View Code Duplication
			elseif (!empty($row['email_address']))
1024
			{
1025
				$ban_items[$row['id_ban']]['type'] = 'email';
1026
				$ban_items[$row['id_ban']]['email'] = str_replace('%', '*', $row['email_address']);
1027
				$log_info[] = array(
1028
					'bantype' => 'email',
1029
					'value' => $ban_items[$row['id_ban']]['email'],
1030
				);
1031
			}
1032
			elseif (!empty($row['id_member']))
1033
			{
1034
				$ban_items[$row['id_ban']]['type'] = 'user';
1035
				$ban_items[$row['id_ban']]['user'] = array(
1036
					'id' => $row['id_member'],
1037
					'name' => $row['real_name'],
1038
					'href' => $scripturl . '?action=profile;u=' . $row['id_member'],
1039
					'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>',
1040
				);
1041
				$log_info[] = array(
1042
					'bantype' => 'user',
1043
					'value' => $row['id_member'],
1044
				);
1045
			}
1046
		}
1047
	}
1048
1049
	// Log this!
1050
	logTriggersUpdates($log_info, false, true);
1051
1052
	$smcFunc['db_free_result']($request);
1053
1054
	if ($group_id !== false)
1055
	{
1056
		$smcFunc['db_query']('', '
1057
			DELETE FROM {db_prefix}ban_items
1058
			WHERE id_ban IN ({array_int:ban_list})
1059
				AND id_ban_group = {int:ban_group}',
1060
			array(
1061
				'ban_list' => $items_ids,
1062
				'ban_group' => $group_id,
1063
			)
1064
		);
1065
	}
1066
	elseif (!empty($items_ids))
1067
	{
1068
		$smcFunc['db_query']('', '
1069
			DELETE FROM {db_prefix}ban_items
1070
			WHERE id_ban IN ({array_int:ban_list})',
1071
			array(
1072
				'ban_list' => $items_ids,
1073
			)
1074
		);
1075
	}
1076
1077
	return true;
1078
}
1079
1080
/**
1081
 * This function removes a bunch of ban groups based on ids
1082
 * Doesn't clean the inputs
1083
 *
1084
 * @param int[] $group_ids The IDs of the groups to remove
1085
 * @return bool Returns ture if successful or false if $group_ids is empty
1086
 */
1087
function removeBanGroups($group_ids)
1088
{
1089
	global $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1090
1091
	if (!is_array($group_ids))
1092
		$group_ids = array($group_ids);
1093
1094
	$group_ids = array_unique($group_ids);
1095
1096
	if (empty($group_ids))
1097
		return false;
1098
1099
	$smcFunc['db_query']('', '
1100
		DELETE FROM {db_prefix}ban_groups
1101
		WHERE id_ban_group IN ({array_int:ban_list})',
1102
		array(
1103
			'ban_list' => $group_ids,
1104
		)
1105
	);
1106
1107
	return true;
1108
}
1109
1110
/**
1111
 * Removes logs - by default truncate the table
1112
 * Doesn't clean the inputs
1113
 *
1114
 * @param array $ids Empty array to clear the ban log or the IDs of the log entries to remove
1115
 * @return bool Returns true if successful or false if $ids is invalid
1116
 */
1117
function removeBanLogs($ids = array())
1118
{
1119
	global $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1120
1121
	if (empty($ids))
1122
		$smcFunc['db_query']('truncate_table', '
1123
			TRUNCATE {db_prefix}log_banned',
1124
			array(
1125
			)
1126
		);
1127
	else
1128
	{
1129
		if (!is_array($ids))
1130
			$ids = array($ids);
1131
1132
		$ids = array_unique($ids);
1133
1134
		if (empty($ids))
1135
			return false;
1136
1137
		$smcFunc['db_query']('', '
1138
			DELETE FROM {db_prefix}log_banned
1139
			WHERE id_ban_log IN ({array_int:ban_list})',
1140
			array(
1141
				'ban_list' => $ids,
1142
			)
1143
		);
1144
	}
1145
1146
	return true;
1147
}
1148
1149
/**
1150
 * This function validates the ban triggers
1151
 *
1152
 * Errors in $context['ban_errors']
1153
 *
1154
 * @param array $triggers The triggers to validate
1155
 * @return array An array of riggers and log info ready to be used
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,array>.

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

Loading history...
1156
 */
1157
function validateTriggers(&$triggers)
1158
{
1159
	global $context, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1160
1161
	if (empty($triggers))
1162
		$context['ban_erros'][] = 'ban_empty_triggers';
1163
1164
	$ban_triggers = array();
1165
	$log_info = array();
1166
1167
	foreach ($triggers as $key => $value)
1168
	{
1169
		if (!empty($value))
1170
		{
1171
			if ($key == 'member')
1172
				continue;
1173
1174
			if ($key == 'main_ip')
1175
			{
1176
				$value = trim($value);
1177
				$ip_parts = ip2range($value);
1178
				if (!checkExistingTriggerIP($ip_parts, $value))
1179
					$context['ban_erros'][] = 'invalid_ip';
1180
				else
1181
				{
1182
					$ban_triggers['main_ip'] = array(
1183
						'ip_low' => $ip_parts['low'],
1184
						'ip_high' => $ip_parts['high']
1185
					);
1186
				}
1187
			}
1188
			elseif ($key == 'hostname')
1189
			{
1190
				if (preg_match('/[^\w.\-*]/', $value) == 1)
1191
					$context['ban_erros'][] = 'invalid_hostname';
1192
				else
1193
				{
1194
					// Replace the * wildcard by a MySQL wildcard %.
1195
					$value = substr(str_replace('*', '%', $value), 0, 255);
1196
1197
					$ban_triggers['hostname']['hostname'] = $value;
1198
				}
1199
			}
1200
			elseif ($key == 'email')
1201
			{
1202
				if (preg_match('/[^\w.\-\+*@]/', $value) == 1)
1203
					$context['ban_erros'][] = 'invalid_email';
1204
1205
				// Check the user is not banning an admin.
1206
				$request = $smcFunc['db_query']('', '
1207
					SELECT id_member
1208
					FROM {db_prefix}members
1209
					WHERE (id_group = {int:admin_group} OR FIND_IN_SET({int:admin_group}, additional_groups) != 0)
1210
						AND email_address LIKE {string:email}
1211
					LIMIT 1',
1212
					array(
1213
						'admin_group' => 1,
1214
						'email' => $value,
1215
					)
1216
				);
1217
				if ($smcFunc['db_num_rows']($request) != 0)
1218
					$context['ban_erros'][] = 'no_ban_admin';
1219
				$smcFunc['db_free_result']($request);
1220
1221
				$value = substr(strtolower(str_replace('*', '%', $value)), 0, 255);
1222
1223
				$ban_triggers['email']['email_address'] = $value;
1224
			}
1225
			elseif ($key == 'user')
1226
			{
1227
				$user = preg_replace('~&amp;#(\d{4,5}|[2-9]\d{2,4}|1[2-9]\d);~', '&#$1;', $smcFunc['htmlspecialchars']($value, ENT_QUOTES));
1228
1229
				$request = $smcFunc['db_query']('', '
1230
					SELECT id_member, (id_group = {int:admin_group} OR FIND_IN_SET({int:admin_group}, additional_groups) != 0) AS isAdmin
1231
					FROM {db_prefix}members
1232
					WHERE member_name = {string:username} OR real_name = {string:username}
1233
					LIMIT 1',
1234
					array(
1235
						'admin_group' => 1,
1236
						'username' => $user,
1237
					)
1238
				);
1239
				if ($smcFunc['db_num_rows']($request) == 0)
1240
					$context['ban_erros'][] = 'invalid_username';
1241
				list ($value, $isAdmin) = $smcFunc['db_fetch_row']($request);
1242
				$smcFunc['db_free_result']($request);
1243
1244
				if ($isAdmin && strtolower($isAdmin) != 'f')
1245
				{
1246
					unset($value);
1247
					$context['ban_erros'][] = 'no_ban_admin';
1248
				}
1249
				else
1250
					$ban_triggers['user']['id_member'] = $value;
1251
			}
1252
			elseif (in_array($key, array('ips_in_messages', 'ips_in_errors')))
1253
			{
1254
				// Special case, those two are arrays themselves
1255
				$values = array_unique($value);
1256
				// Don't add the main IP again.
1257
				if (isset($triggers['main_ip']))
1258
					$values = array_diff($values, array($triggers['main_ip']));
1259
				unset($value);
1260
				foreach ($values as $val)
1261
				{
1262
					$val = trim($val);
1263
					$ip_parts = ip2range($val);
1264
					if (!checkExistingTriggerIP($ip_parts, $val))
1265
						$context['ban_erros'][] = 'invalid_ip';
1266
					else
1267
					{
1268
						$ban_triggers[$key][] = array(
1269
							'ip_low' => $ip_parts['low'],
1270
							'ip_high' => $ip_parts['high'],
1271
						);
1272
1273
						$log_info[] = array(
1274
							'value' => $val,
1275
							'bantype' => 'ip_range',
1276
						);
1277
					}
1278
				}
1279
			}
1280
			else
1281
				$context['ban_erros'][] = 'no_bantype_selected';
1282
1283
			if (isset($value) && !is_array($value))
1284
				$log_info[] = array(
1285
					'value' => $value,
1286
					'bantype' => $key,
1287
				);
1288
		}
1289
	}
1290
	return array('ban_triggers' => $ban_triggers, 'log_info' => $log_info);
1291
}
1292
1293
/**
1294
 * This function actually inserts the ban triggers into the database
1295
 *
1296
 * Errors in $context['ban_errors']
1297
 *
1298
 * @param int $group_id The ID of the group to add the triggers to (0 to create a new one)
1299
 * @param array $triggers The triggers to add
1300
 * @param array $logs The log data
1301
 * @return bool Whether or not the action was successful
1302
 */
1303
function addTriggers($group_id = 0, $triggers = array(), $logs = array())
1304
{
1305
	global $smcFunc, $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1306
1307
	if (empty($group_id))
1308
		$context['ban_errors'][] = 'ban_id_empty';
1309
1310
	// Preset all values that are required.
1311
	$values = array(
1312
		'id_ban_group' => $group_id,
1313
		'hostname' => '',
1314
		'email_address' => '',
1315
		'id_member' => 0,
1316
		'ip_low' => 'null',
1317
		'ip_high' => 'null',
1318
	);
1319
1320
	$insertKeys = array(
1321
		'id_ban_group' => 'int',
1322
		'hostname' => 'string',
1323
		'email_address' => 'string',
1324
		'id_member' => 'int',
1325
		'ip_low' => 'inet',
1326
		'ip_high' => 'inet',
1327
	);
1328
1329
	$insertTriggers = array();
1330
	foreach ($triggers as $key => $trigger)
1331
	{
1332
		// Exceptions, exceptions, exceptions...always exceptions... :P
1333
		if (in_array($key, array('ips_in_messages', 'ips_in_errors')))
1334
			foreach ($trigger as $real_trigger)
1335
				$insertTriggers[] = array_merge($values, $real_trigger);
1336
		else
1337
			$insertTriggers[] = array_merge($values, $trigger);
1338
	}
1339
1340
	if (empty($insertTriggers))
1341
		$context['ban_errors'][] = 'ban_no_triggers';
1342
1343
	if (!empty($context['ban_errors']))
1344
		return false;
1345
1346
	$smcFunc['db_insert']('',
1347
		'{db_prefix}ban_items',
1348
		$insertKeys,
1349
		$insertTriggers,
1350
		array('id_ban')
1351
	);
1352
1353
	logTriggersUpdates($logs, true);
1354
1355
	return true;
1356
}
1357
1358
/**
1359
 * This function updates an existing ban trigger into the database
1360
 *
1361
 * Errors in $context['ban_errors']
1362
 *
1363
 * @param int $ban_item The ID of the ban item
1364
 * @param int $group_id The ID of the ban group
1365
 * @param array $trigger An array of triggers
1366
 * @param array $logs An array of log info
1367
 */
1368
function updateTriggers($ban_item = 0, $group_id = 0, $trigger = array(), $logs = array())
1369
{
1370
	global $smcFunc, $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1371
1372
	if (empty($ban_item))
1373
		$context['ban_errors'][] = 'ban_ban_item_empty';
1374
	if (empty($group_id))
1375
		$context['ban_errors'][] = 'ban_id_empty';
1376
	if (empty($trigger))
1377
		$context['ban_errors'][] = 'ban_no_triggers';
1378
1379
	if (!empty($context['ban_errors']))
1380
		return;
1381
1382
	// Preset all values that are required.
1383
	$values = array(
1384
		'id_ban_group' => $group_id,
1385
		'hostname' => '',
1386
		'email_address' => '',
1387
		'id_member' => 0,
1388
		'ip_low' => 'null',
1389
		'ip_high' => 'null',
1390
	);
1391
1392
	$trigger = array_merge($values, $trigger);
1393
1394
	$smcFunc['db_query']('', '
1395
		UPDATE {db_prefix}ban_items
1396
		SET
1397
			hostname = {string:hostname}, email_address = {string:email_address}, id_member = {int:id_member},
1398
			ip_low = {inet:ip_low}, ip_high = {inet:ip_high}
1399
		WHERE id_ban = {int:ban_item}
1400
			AND id_ban_group = {int:id_ban_group}',
1401
		array_merge($trigger, array(
1402
			'id_ban_group' => $group_id,
1403
			'ban_item' => $ban_item,
1404
		))
1405
	);
1406
1407
	logTriggersUpdates($logs, false);
1408
}
1409
1410
/**
1411
 * A small function to unify logging of triggers (updates and new)
1412
 *
1413
 * @param array $logs an array of logs, each log contains the following keys:
1414
 *                - bantype: a known type of ban (ip_range, hostname, email, user, main_ip)
1415
 *                - value: the value of the bantype (e.g. the IP or the email address banned)
1416
 * @param bool $new Whether the trigger is new or an update of an existing one
1417
 * @param bool $removal Whether the trigger is being deleted
1418
 */
1419
function logTriggersUpdates($logs, $new = true, $removal = false)
1420
{
1421
	if (empty($logs))
1422
		return;
1423
1424
	$log_name_map = array(
1425
		'main_ip' => 'ip_range',
1426
		'hostname' => 'hostname',
1427
		'email' => 'email',
1428
		'user' => 'member',
1429
		'ip_range' => 'ip_range',
1430
	);
1431
1432
	// Log the addion of the ban entries into the moderation log.
1433
	foreach ($logs as $log)
1434
		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...
1435
			$log_name_map[$log['bantype']] => $log['value'],
1436
			'new' => empty($new) ? 0 : 1,
1437
			'remove' => empty($removal) ? 0 : 1,
1438
			'type' => $log['bantype'],
1439
		));
1440
}
1441
1442
/**
1443
 * Updates an existing ban group
1444
 *
1445
 * Errors in $context['ban_errors']
1446
 *
1447
 * @param array $ban_info An array of info about the ban group. Should have name and may also have an id.
1448
 * @return int The ban group's ID
1449
 */
1450
function updateBanGroup($ban_info = array())
1451
{
1452
	global $smcFunc, $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1453
1454
	if (empty($ban_info['name']))
1455
		$context['ban_errors'][] = 'ban_name_empty';
1456
	if (empty($ban_info['id']))
1457
		$context['ban_errors'][] = 'ban_id_empty';
1458 View Code Duplication
	if (empty($ban_info['cannot']['access']) && empty($ban_info['cannot']['register']) && empty($ban_info['cannot']['post']) && empty($ban_info['cannot']['login']))
1459
		$context['ban_errors'][] = 'ban_unknown_restriction_type';
1460
1461
	if (!empty($ban_info['id']))
1462
	{
1463
		// Verify the ban group exists.
1464
		$request = $smcFunc['db_query']('', '
1465
			SELECT id_ban_group
1466
			FROM {db_prefix}ban_groups
1467
			WHERE id_ban_group = {int:ban_group}
1468
			LIMIT 1',
1469
			array(
1470
				'ban_group' => $ban_info['id']
1471
			)
1472
		);
1473
1474
		if ($smcFunc['db_num_rows']($request) == 0)
1475
			$context['ban_errors'][] = 'ban_not_found';
1476
		$smcFunc['db_free_result']($request);
1477
	}
1478
1479
	if (!empty($ban_info['name']))
1480
	{
1481
		// Make sure the name does not already exist (Of course, if it exists in the ban group we are editing, proceed.)
1482
		$request = $smcFunc['db_query']('', '
1483
			SELECT id_ban_group
1484
			FROM {db_prefix}ban_groups
1485
			WHERE name = {string:new_ban_name}
1486
				AND id_ban_group != {int:ban_group}
1487
			LIMIT 1',
1488
			array(
1489
				'ban_group' => empty($ban_info['id']) ? 0 : $ban_info['id'],
1490
				'new_ban_name' => $ban_info['name'],
1491
			)
1492
		);
1493
		if ($smcFunc['db_num_rows']($request) != 0)
1494
			$context['ban_errors'][] = 'ban_name_exists';
1495
		$smcFunc['db_free_result']($request);
1496
	}
1497
1498
	if (!empty($context['ban_errors']))
1499
		return $ban_info['id'];
1500
1501
	$smcFunc['db_query']('', '
1502
		UPDATE {db_prefix}ban_groups
1503
		SET
1504
			name = {string:ban_name},
1505
			reason = {string:reason},
1506
			notes = {string:notes},
1507
			expire_time = {raw:expiration},
1508
			cannot_access = {int:cannot_access},
1509
			cannot_post = {int:cannot_post},
1510
			cannot_register = {int:cannot_register},
1511
			cannot_login = {int:cannot_login}
1512
		WHERE id_ban_group = {int:id_ban_group}',
1513
		array(
1514
			'expiration' => $ban_info['db_expiration'],
1515
			'cannot_access' => $ban_info['cannot']['access'],
1516
			'cannot_post' => $ban_info['cannot']['post'],
1517
			'cannot_register' => $ban_info['cannot']['register'],
1518
			'cannot_login' => $ban_info['cannot']['login'],
1519
			'id_ban_group' => $ban_info['id'],
1520
			'ban_name' => $ban_info['name'],
1521
			'reason' => $ban_info['reason'],
1522
			'notes' => $ban_info['notes'],
1523
		)
1524
	);
1525
1526
	return $ban_info['id'];
1527
}
1528
1529
/**
1530
 * Creates a new ban group
1531
 * If the group is successfully created the ID is returned
1532
 * On error the error code is returned or false
1533
 *
1534
 * Errors in $context['ban_errors']
1535
 *
1536
 * @param array $ban_info An array containing 'name', which is the name of the ban group
1537
 * @return int The ban group's ID
1538
 */
1539
function insertBanGroup($ban_info = array())
1540
{
1541
	global $smcFunc, $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1542
1543
	if (empty($ban_info['name']))
1544
		$context['ban_errors'][] = 'ban_name_empty';
1545 View Code Duplication
	if (empty($ban_info['cannot']['access']) && empty($ban_info['cannot']['register']) && empty($ban_info['cannot']['post']) && empty($ban_info['cannot']['login']))
1546
		$context['ban_errors'][] = 'ban_unknown_restriction_type';
1547
1548
	if (!empty($ban_info['name']))
1549
	{
1550
		// Check whether a ban with this name already exists.
1551
		$request = $smcFunc['db_query']('', '
1552
			SELECT id_ban_group
1553
			FROM {db_prefix}ban_groups
1554
			WHERE name = {string:new_ban_name}' . '
1555
			LIMIT 1',
1556
			array(
1557
				'new_ban_name' => $ban_info['name'],
1558
			)
1559
		);
1560
1561
		if ($smcFunc['db_num_rows']($request) == 1)
1562
			$context['ban_errors'][] = 'ban_name_exists';
1563
		$smcFunc['db_free_result']($request);
1564
	}
1565
1566
	if (!empty($context['ban_errors']))
1567
		return;
1568
1569
	// Yes yes, we're ready to add now.
1570
	$ban_info['id'] = $smcFunc['db_insert']('',
1571
		'{db_prefix}ban_groups',
1572
		array(
1573
			'name' => 'string-20', 'ban_time' => 'int', 'expire_time' => 'raw', 'cannot_access' => 'int', 'cannot_register' => 'int',
1574
			'cannot_post' => 'int', 'cannot_login' => 'int', 'reason' => 'string-255', 'notes' => 'string-65534',
1575
		),
1576
		array(
1577
			$ban_info['name'], time(), $ban_info['db_expiration'], $ban_info['cannot']['access'], $ban_info['cannot']['register'],
1578
			$ban_info['cannot']['post'], $ban_info['cannot']['login'], $ban_info['reason'], $ban_info['notes'],
1579
		),
1580
		array('id_ban_group'),
1581
		1
1582
	);
1583
1584
	if (empty($ban_info['id']))
1585
		$context['ban_errors'][] = 'impossible_insert_new_bangroup';
1586
1587
	return $ban_info['id'];
1588
}
1589
1590
/**
1591
 * This function handles the ins and outs of the screen for adding new ban
1592
 * triggers or modifying existing ones.
1593
 * Adding new ban triggers:
1594
 * 	- is accessed by ?action=admin;area=ban;sa=edittrigger;bg=x
1595
 * 	- uses the ban_edit_trigger sub template of ManageBans.
1596
 * Editing existing ban triggers:
1597
 *  - is accessed by ?action=admin;area=ban;sa=edittrigger;bg=x;bi=y
1598
 *  - uses the ban_edit_trigger sub template of ManageBans.
1599
 */
1600
function BanEditTrigger()
1601
{
1602
	global $context, $smcFunc, $scripturl;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1603
1604
	$context['sub_template'] = 'ban_edit_trigger';
1605
	$context['form_url'] = $scripturl . '?action=admin;area=ban;sa=edittrigger';
1606
1607
	$ban_group = isset($_REQUEST['bg']) ? (int) $_REQUEST['bg'] : 0;
1608
	$ban_id = isset($_REQUEST['bi']) ? (int) $_REQUEST['bi'] : 0;
1609
1610
	if (empty($ban_group))
1611
		fatal_lang_error('ban_not_found', false);
1612
1613
	if (isset($_POST['add_new_trigger']) && !empty($_POST['ban_suggestions']))
1614
	{
1615
		saveTriggers($_POST['ban_suggestions'], $ban_group, 0, $ban_id);
1616
		redirectexit('action=admin;area=ban;sa=edit' . (!empty($ban_group) ? ';bg=' . $ban_group : ''));
1617
	}
1618
	elseif (isset($_POST['edit_trigger']) && !empty($_POST['ban_suggestions']))
1619
	{
1620
		// The first replaces the old one, the others are added new (simplification, otherwise it would require another query and some work...)
1621
		saveTriggers(array_shift($_POST['ban_suggestions']), $ban_group, 0, $ban_id);
1622
		if (!empty($_POST['ban_suggestions']))
1623
			saveTriggers($_POST['ban_suggestions'], $ban_group);
1624
1625
		redirectexit('action=admin;area=ban;sa=edit' . (!empty($ban_group) ? ';bg=' . $ban_group : ''));
1626
	}
1627
	elseif (isset($_POST['edit_trigger']))
1628
	{
1629
		removeBanTriggers($ban_id);
0 ignored issues
show
Documentation introduced by
$ban_id is of type integer, but the function expects a array.

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...
1630
		redirectexit('action=admin;area=ban;sa=edit' . (!empty($ban_group) ? ';bg=' . $ban_group : ''));
1631
	}
1632
1633
	loadJavaScriptFile('suggest.js', array('minimize' => true), 'smf_suggest');
1634
1635
	if (empty($ban_id))
1636
	{
1637
		$context['ban_trigger'] = array(
1638
			'id' => 0,
1639
			'group' => $ban_group,
1640
			'ip' => array(
1641
				'value' => '',
1642
				'selected' => true,
1643
			),
1644
			'hostname' => array(
1645
				'selected' => false,
1646
				'value' => '',
1647
			),
1648
			'email' => array(
1649
				'value' => '',
1650
				'selected' => false,
1651
			),
1652
			'banneduser' => array(
1653
				'value' => '',
1654
				'selected' => false,
1655
			),
1656
			'is_new' => true,
1657
		);
1658
	}
1659
	else
1660
	{
1661
		$request = $smcFunc['db_query']('', '
1662
			SELECT
1663
				bi.id_ban, bi.id_ban_group, bi.hostname, bi.email_address, bi.id_member,
1664
				bi.ip_low, bi.ip_high,
1665
				mem.member_name, mem.real_name
1666
			FROM {db_prefix}ban_items AS bi
1667
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = bi.id_member)
1668
			WHERE bi.id_ban = {int:ban_item}
1669
				AND bi.id_ban_group = {int:ban_group}
1670
			LIMIT 1',
1671
			array(
1672
				'ban_item' => $ban_id,
1673
				'ban_group' => $ban_group,
1674
			)
1675
		);
1676
		if ($smcFunc['db_num_rows']($request) == 0)
1677
			fatal_lang_error('ban_not_found', false);
1678
		$row = $smcFunc['db_fetch_assoc']($request);
1679
		$smcFunc['db_free_result']($request);
1680
1681
		$context['ban_trigger'] = array(
1682
			'id' => $row['id_ban'],
1683
			'group' => $row['id_ban_group'],
1684
			'ip' => array(
1685
				'value' => empty($row['ip_low']) ? '' : range2ip($row['ip_low'], $row['ip_high']),
1686
				'selected' => !empty($row['ip_low']),
1687
			),
1688
			'hostname' => array(
1689
				'value' => str_replace('%', '*', $row['hostname']),
1690
				'selected' => !empty($row['hostname']),
1691
			),
1692
			'email' => array(
1693
				'value' => str_replace('%', '*', $row['email_address']),
1694
				'selected' => !empty($row['email_address'])
1695
			),
1696
			'banneduser' => array(
1697
				'value' => $row['member_name'],
1698
				'selected' => !empty($row['member_name'])
1699
			),
1700
			'is_new' => false,
1701
		);
1702
	}
1703
1704
	createToken('admin-bet');
1705
}
1706
1707
/**
1708
 * This handles the screen for showing the banned entities
1709
 * It is accessed by ?action=admin;area=ban;sa=browse
1710
 * It uses sub-tabs for browsing by IP, hostname, email or username.
1711
 *
1712
 * @uses ManageBans template, browse_triggers sub template.
1713
 */
1714
function BanBrowseTriggers()
1715
{
1716
	global $modSettings, $context, $scripturl, $smcFunc, $txt;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1717
	global $sourcedir, $settings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1718
1719
	if (!empty($_POST['remove_triggers']) && !empty($_POST['remove']) && is_array($_POST['remove']))
1720
	{
1721
		checkSession();
1722
1723
		removeBanTriggers($_POST['remove']);
1724
1725
		// Rehabilitate some members.
1726
		if ($_REQUEST['entity'] == 'member')
1727
			updateBanMembers();
1728
1729
		// Make sure the ban cache is refreshed.
1730
		updateSettings(array('banLastUpdated' => time()));
1731
	}
1732
1733
	$context['selected_entity'] = isset($_REQUEST['entity']) && in_array($_REQUEST['entity'], array('ip', 'hostname', 'email', 'member')) ? $_REQUEST['entity'] : 'ip';
1734
1735
	$listOptions = array(
1736
		'id' => 'ban_trigger_list',
1737
		'title' => $txt['ban_trigger_browse'],
1738
		'items_per_page' => $modSettings['defaultMaxListItems'],
1739
		'base_href' => $scripturl . '?action=admin;area=ban;sa=browse;entity=' . $context['selected_entity'],
1740
		'default_sort_col' => 'banned_entity',
1741
		'no_items_label' => $txt['ban_no_triggers'],
1742
		'get_items' => array(
1743
			'function' => 'list_getBanTriggers',
1744
			'params' => array(
1745
				$context['selected_entity'],
1746
			),
1747
		),
1748
		'get_count' => array(
1749
			'function' => 'list_getNumBanTriggers',
1750
			'params' => array(
1751
				$context['selected_entity'],
1752
			),
1753
		),
1754
		'columns' => array(
1755
			'banned_entity' => array(
1756
				'header' => array(
1757
					'value' => $txt['ban_banned_entity'],
1758
				),
1759
			),
1760
			'ban_name' => array(
1761
				'header' => array(
1762
					'value' => $txt['ban_name'],
1763
				),
1764
				'data' => array(
1765
					'sprintf' => array(
1766
						'format' => '<a href="' . $scripturl . '?action=admin;area=ban;sa=edit;bg=%1$d">%2$s</a>',
1767
						'params' => array(
1768
							'id_ban_group' => false,
1769
							'name' => false,
1770
						),
1771
					),
1772
				),
1773
				'sort' => array(
1774
					'default' => 'bg.name',
1775
					'reverse' => 'bg.name DESC',
1776
				),
1777
			),
1778
			'hits' => array(
1779
				'header' => array(
1780
					'value' => $txt['ban_hits'],
1781
				),
1782
				'data' => array(
1783
					'db' => 'hits',
1784
				),
1785
				'sort' => array(
1786
					'default' => 'bi.hits DESC',
1787
					'reverse' => 'bi.hits',
1788
				),
1789
			),
1790
			'check' => array(
1791
				'header' => array(
1792
					'value' => '<input type="checkbox" onclick="invertAll(this, this.form);">',
1793
					'class' => 'centercol',
1794
				),
1795
				'data' => array(
1796
					'sprintf' => array(
1797
						'format' => '<input type="checkbox" name="remove[]" value="%1$d">',
1798
						'params' => array(
1799
							'id_ban' => false,
1800
						),
1801
					),
1802
					'class' => 'centercol',
1803
				),
1804
			),
1805
		),
1806
		'form' => array(
1807
			'href' => $scripturl . '?action=admin;area=ban;sa=browse;entity=' . $context['selected_entity'],
1808
			'include_start' => true,
1809
			'include_sort' => true,
1810
		),
1811
		'additional_rows' => array(
1812
			array(
1813
				'position' => 'above_column_headers',
1814
				'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>',
1815
			),
1816
			array(
1817
				'position' => 'bottom_of_list',
1818
				'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">',
1819
			),
1820
		),
1821
	);
1822
1823
	// Specific data for the first column depending on the selected entity.
1824
	if ($context['selected_entity'] === 'ip')
1825
	{
1826
		$listOptions['columns']['banned_entity']['data'] = array(
1827
			'function' => function($rowData)
1828
			{
1829
				return range2ip(
1830
					$rowData['ip_low']
1831
				,
1832
					$rowData['ip_high']
1833
				);
1834
			},
1835
		);
1836
		$listOptions['columns']['banned_entity']['sort'] = array(
1837
			'default' => 'bi.ip_low, bi.ip_high, bi.ip_low',
1838
			'reverse' => 'bi.ip_low DESC, bi.ip_high DESC',
1839
		);
1840
	}
1841 View Code Duplication
	elseif ($context['selected_entity'] === 'hostname')
1842
	{
1843
		$listOptions['columns']['banned_entity']['data'] = array(
1844
			'function' => function($rowData) use ($smcFunc)
1845
			{
1846
				return strtr($smcFunc['htmlspecialchars']($rowData['hostname']), array('%' => '*'));
1847
			},
1848
		);
1849
		$listOptions['columns']['banned_entity']['sort'] = array(
1850
			'default' => 'bi.hostname',
1851
			'reverse' => 'bi.hostname DESC',
1852
		);
1853
	}
1854 View Code Duplication
	elseif ($context['selected_entity'] === 'email')
1855
	{
1856
		$listOptions['columns']['banned_entity']['data'] = array(
1857
			'function' => function($rowData) use ($smcFunc)
1858
			{
1859
				return strtr($smcFunc['htmlspecialchars']($rowData['email_address']), array('%' => '*'));
1860
			},
1861
		);
1862
		$listOptions['columns']['banned_entity']['sort'] = array(
1863
			'default' => 'bi.email_address',
1864
			'reverse' => 'bi.email_address DESC',
1865
		);
1866
	}
1867
	elseif ($context['selected_entity'] === 'member')
1868
	{
1869
		$listOptions['columns']['banned_entity']['data'] = array(
1870
			'sprintf' => array(
1871
				'format' => '<a href="' . $scripturl . '?action=profile;u=%1$d">%2$s</a>',
1872
				'params' => array(
1873
					'id_member' => false,
1874
					'real_name' => false,
1875
				),
1876
			),
1877
		);
1878
		$listOptions['columns']['banned_entity']['sort'] = array(
1879
			'default' => 'mem.real_name',
1880
			'reverse' => 'mem.real_name DESC',
1881
		);
1882
	}
1883
1884
	// Create the list.
1885
	require_once($sourcedir . '/Subs-List.php');
1886
	createList($listOptions);
1887
1888
	// The list is the only thing to show, so make it the default sub template.
1889
	$context['sub_template'] = 'show_list';
1890
	$context['default_list'] = 'ban_trigger_list';
1891
}
1892
1893
/**
1894
 * Get ban triggers for the given parameters. Callback from $listOptions['get_items'] in BanBrowseTriggers()
1895
 *
1896
 * @param int $start The item to start with (for pagination purposes)
1897
 * @param int $items_per_page How many items to show on each page
1898
 * @param string $sort A string telling ORDER BY how to sort the results
1899
 * @param string $trigger_type The trigger type - can be 'ip', 'hostname' or 'email'
1900
 * @return array An array of ban trigger info for the list
1901
 */
1902
function list_getBanTriggers($start, $items_per_page, $sort, $trigger_type)
1903
{
1904
	global $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1905
1906
	$where = array(
1907
		'ip' => 'bi.ip_low is not null',
1908
		'hostname' => 'bi.hostname != {string:blank_string}',
1909
		'email' => 'bi.email_address != {string:blank_string}',
1910
	);
1911
1912
	$request = $smcFunc['db_query']('', '
1913
		SELECT
1914
			bi.id_ban, bi.ip_low, bi.ip_high, bi.hostname, bi.email_address, bi.hits,
1915
			bg.id_ban_group, bg.name' . ($trigger_type === 'member' ? ',
1916
			mem.id_member, mem.real_name' : '') . '
1917
		FROM {db_prefix}ban_items AS bi
1918
			INNER JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group)' . ($trigger_type === 'member' ? '
1919
			INNER JOIN {db_prefix}members AS mem ON (mem.id_member = bi.id_member)' : '
1920
		WHERE ' . $where[$trigger_type]) . '
1921
		ORDER BY {raw:sort}
1922
		LIMIT {int:start}, {int:max}',
1923
		array(
1924
			'blank_string' => '',
1925
			'sort' => $sort,
1926
			'start' => $start,
1927
			'max' => $items_per_page,
1928
		)
1929
	);
1930
	$ban_triggers = array();
1931
	while ($row = $smcFunc['db_fetch_assoc']($request))
1932
		$ban_triggers[] = $row;
1933
	$smcFunc['db_free_result']($request);
1934
1935
	return $ban_triggers;
1936
}
1937
1938
/**
1939
 * This returns the total number of ban triggers of the given type. Callback for $listOptions['get_count'] in BanBrowseTriggers().
1940
 *
1941
 * @param string $trigger_type The trigger type. Can be 'ip', 'hostname' or 'email'
1942
 * @return int The number of triggers of the specified type
1943
 */
1944
function list_getNumBanTriggers($trigger_type)
1945
{
1946
	global $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1947
1948
	$where = array(
1949
		'ip' => 'bi.ip_low is not null',
1950
		'hostname' => 'bi.hostname != {string:blank_string}',
1951
		'email' => 'bi.email_address != {string:blank_string}',
1952
	);
1953
1954
	$request = $smcFunc['db_query']('', '
1955
		SELECT COUNT(*)
1956
		FROM {db_prefix}ban_items AS bi' . ($trigger_type === 'member' ? '
1957
			INNER JOIN {db_prefix}members AS mem ON (mem.id_member = bi.id_member)' : '
1958
		WHERE ' . $where[$trigger_type]),
1959
		array(
1960
			'blank_string' => '',
1961
		)
1962
	);
1963
	list ($num_triggers) = $smcFunc['db_fetch_row']($request);
1964
	$smcFunc['db_free_result']($request);
1965
1966
	return $num_triggers;
1967
}
1968
1969
/**
1970
 * This handles the listing of ban log entries, and allows their deletion.
1971
 * Shows a list of logged access attempts by banned users.
1972
 * It is accessed by ?action=admin;area=ban;sa=log.
1973
 * How it works:
1974
 *  - allows sorting of several columns.
1975
 *  - also handles deletion of (a selection of) log entries.
1976
 */
1977
function BanLog()
1978
{
1979
	global $scripturl, $context, $sourcedir, $txt, $modSettings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1980
1981
	// Delete one or more entries.
1982
	if (!empty($_POST['removeAll']) || (!empty($_POST['removeSelected']) && !empty($_POST['remove'])))
1983
	{
1984
		checkSession();
1985
		validateToken('admin-bl');
1986
1987
		// 'Delete all entries' button was pressed.
1988
		if (!empty($_POST['removeAll']))
1989
			removeBanLogs();
1990
		// 'Delete selection' button was pressed.
1991
		else
1992
		{
1993
			array_map('intval', $_POST['remove']);
1994
			removeBanLogs($_POST['remove']);
1995
		}
1996
	}
1997
1998
	$listOptions = array(
1999
		'id' => 'ban_log',
2000
		'title' => $txt['ban_log'],
2001
		'items_per_page' => $modSettings['defaultMaxListItems'],
2002
		'base_href' => $context['admin_area'] == 'ban' ? $scripturl . '?action=admin;area=ban;sa=log' : $scripturl . '?action=admin;area=logs;sa=banlog',
2003
		'default_sort_col' => 'date',
2004
		'get_items' => array(
2005
			'function' => 'list_getBanLogEntries',
2006
		),
2007
		'get_count' => array(
2008
			'function' => 'list_getNumBanLogEntries',
2009
		),
2010
		'no_items_label' => $txt['ban_log_no_entries'],
2011
		'columns' => array(
2012
			'ip' => array(
2013
				'header' => array(
2014
					'value' => $txt['ban_log_ip'],
2015
				),
2016
				'data' => array(
2017
					'sprintf' => array(
2018
						'format' => '<a href="' . $scripturl . '?action=trackip;searchip=%1$s">%1$s</a>',
2019
						'params' => array(
2020
							'ip' => false,
2021
						),
2022
					),
2023
				),
2024
				'sort' => array(
2025
					'default' => 'lb.ip',
2026
					'reverse' => 'lb.ip DESC',
2027
				),
2028
			),
2029
			'email' => array(
2030
				'header' => array(
2031
					'value' => $txt['ban_log_email'],
2032
				),
2033
				'data' => array(
2034
					'db_htmlsafe' => 'email',
2035
				),
2036
				'sort' => array(
2037
					'default' => 'lb.email = \'\', lb.email',
2038
					'reverse' => 'lb.email != \'\', lb.email DESC',
2039
				),
2040
			),
2041
			'member' => array(
2042
				'header' => array(
2043
					'value' => $txt['ban_log_member'],
2044
				),
2045
				'data' => array(
2046
					'sprintf' => array(
2047
						'format' => '<a href="' . $scripturl . '?action=profile;u=%1$d">%2$s</a>',
2048
						'params' => array(
2049
							'id_member' => false,
2050
							'real_name' => false,
2051
						),
2052
					),
2053
				),
2054
				'sort' => array(
2055
					'default' => 'COALESCE(mem.real_name, 1=1), mem.real_name',
2056
					'reverse' => 'COALESCE(mem.real_name, 1=1) DESC, mem.real_name DESC',
2057
				),
2058
			),
2059
			'date' => array(
2060
				'header' => array(
2061
					'value' => $txt['ban_log_date'],
2062
				),
2063
				'data' => array(
2064
					'function' => function($rowData)
2065
					{
2066
						return timeformat($rowData['log_time']);
2067
					},
2068
				),
2069
				'sort' => array(
2070
					'default' => 'lb.log_time DESC',
2071
					'reverse' => 'lb.log_time',
2072
				),
2073
			),
2074
			'check' => array(
2075
				'header' => array(
2076
					'value' => '<input type="checkbox" onclick="invertAll(this, this.form);">',
2077
					'class' => 'centercol',
2078
				),
2079
				'data' => array(
2080
					'sprintf' => array(
2081
						'format' => '<input type="checkbox" name="remove[]" value="%1$d">',
2082
						'params' => array(
2083
							'id_ban_log' => false,
2084
						),
2085
					),
2086
					'class' => 'centercol',
2087
				),
2088
			),
2089
		),
2090
		'form' => array(
2091
			'href' => $context['admin_area'] == 'ban' ? $scripturl . '?action=admin;area=ban;sa=log' : $scripturl . '?action=admin;area=logs;sa=banlog',
2092
			'include_start' => true,
2093
			'include_sort' => true,
2094
			'token' => 'admin-bl',
2095
		),
2096
		'additional_rows' => array(
2097
			array(
2098
				'position' => 'top_of_list',
2099
				'value' => '
2100
					<input type="submit" name="removeSelected" value="' . $txt['ban_log_remove_selected'] . '" data-confirm="' . $txt['ban_log_remove_selected_confirm'] . '" class="button you_sure">
2101
					<input type="submit" name="removeAll" value="' . $txt['ban_log_remove_all'] . '" data-confirm="' . $txt['ban_log_remove_all_confirm'] . '" class="button you_sure">',
2102
			),
2103
			array(
2104
				'position' => 'bottom_of_list',
2105
				'value' => '
2106
					<input type="submit" name="removeSelected" value="' . $txt['ban_log_remove_selected'] . '" data-confirm="' . $txt['ban_log_remove_selected_confirm'] . '" class="button you_sure">
2107
					<input type="submit" name="removeAll" value="' . $txt['ban_log_remove_all'] . '" data-confirm="' . $txt['ban_log_remove_all_confirm'] . '" class="button you_sure">',
2108
			),
2109
		),
2110
	);
2111
2112
	createToken('admin-bl');
2113
2114
	require_once($sourcedir . '/Subs-List.php');
2115
	createList($listOptions);
2116
2117
	$context['page_title'] = $txt['ban_log'];
2118
	$context['sub_template'] = 'show_list';
2119
	$context['default_list'] = 'ban_log';
2120
}
2121
2122
/**
2123
 * Load a list of ban log entries from the database.
2124
 * (no permissions check). Callback for $listOptions['get_items'] in BanLog()
2125
 *
2126
 * @param int $start The item to start with (for pagination purposes)
2127
 * @param int $items_per_page How many items to show on each page
2128
 * @param string $sort A string telling ORDER BY how to sort the results
2129
 * @return array An array of info about the ban log entries for the list.
2130
 */
2131
function list_getBanLogEntries($start, $items_per_page, $sort)
2132
{
2133
	global $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2134
2135
	$dash = '-';
2136
2137
	$request = $smcFunc['db_query']('', '
2138
		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
2139
		FROM {db_prefix}log_banned AS lb
2140
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lb.id_member)
2141
		ORDER BY {raw:sort}
2142
		LIMIT {int:start}, {int:items}',
2143
		array(
2144
			'blank_string' => '',
2145
			'dash' => $dash,
2146
			'sort' => $sort,
2147
			'start' => $start,
2148
			'items' => $items_per_page,
2149
		)
2150
	);
2151
	$log_entries = array();
2152 View Code Duplication
	while ($row = $smcFunc['db_fetch_assoc']($request))
2153
	{
2154
		$row['ip'] = $row['ip'] === null ? $dash : inet_dtop($row['ip']);
2155
		$log_entries[] = $row;
2156
	}
2157
	$smcFunc['db_free_result']($request);
2158
2159
	return $log_entries;
2160
}
2161
2162
/**
2163
 * This returns the total count of ban log entries. Callback for $listOptions['get_count'] in BanLog().
2164
 * @return int The total number of ban log entries.
2165
 */
2166
function list_getNumBanLogEntries()
2167
{
2168
	global $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
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
0 ignored issues
show
Documentation introduced by
Should the return type not be string|false?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
2192
 */
2193
function range2ip($low, $high)
2194
{
2195
	$low = inet_dtop($low);
0 ignored issues
show
Documentation introduced by
$low is of type array, but the function expects a string.

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...
2196
	$high = inet_dtop($high);
0 ignored issues
show
Documentation introduced by
$high is of type array, but the function expects a string.

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...
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;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2217
2218
2219
	$values = array(
2220
		'ip_low' => $ip_array['low'],
2221
		'ip_high' => $ip_array['high']
2222
	);
2223
2224
2225
	$request = $smcFunc['db_query']('', '
2226
		SELECT bg.id_ban_group, bg.name
2227
		FROM {db_prefix}ban_groups AS bg
2228
		INNER JOIN {db_prefix}ban_items AS bi ON
2229
			(bi.id_ban_group = bg.id_ban_group)
2230
			AND ip_low = {inet:ip_low} AND ip_high = {inet:ip_high}
2231
		LIMIT 1',
2232
		$values
2233
	);
2234
	if ($smcFunc['db_num_rows']($request) != 0)
2235
	{
2236
		list ($error_id_ban, $error_ban_name) = $smcFunc['db_fetch_row']($request);
2237
		fatal_lang_error('ban_trigger_already_exists', false, array(
2238
			$fullip,
2239
			'<a href="' . $scripturl . '?action=admin;area=ban;sa=edit;bg=' . $error_id_ban . '">' . $error_ban_name . '</a>',
2240
		));
2241
	}
2242
	$smcFunc['db_free_result']($request);
2243
2244
	return $values;
2245
}
2246
2247
/**
2248
 * As it says... this tries to review the list of banned members, to match new bans.
2249
 * Note: is_activated >= 10: a member is banned.
2250
 */
2251
function updateBanMembers()
2252
{
2253
	global $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2254
2255
	$updates = array();
2256
	$allMembers = array();
2257
	$newMembers = array();
2258
2259
	// Start by getting all active bans - it's quicker doing this in parts...
2260
	$request = $smcFunc['db_query']('', '
2261
		SELECT bi.id_member, bi.email_address
2262
		FROM {db_prefix}ban_items AS bi
2263
			INNER JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group)
2264
		WHERE (bi.id_member > {int:no_member} OR bi.email_address != {string:blank_string})
2265
			AND bg.cannot_access = {int:cannot_access_on}
2266
			AND (bg.expire_time IS NULL OR bg.expire_time > {int:current_time})',
2267
		array(
2268
			'no_member' => 0,
2269
			'cannot_access_on' => 1,
2270
			'current_time' => time(),
2271
			'blank_string' => '',
2272
		)
2273
	);
2274
	$memberIDs = array();
2275
	$memberEmails = array();
2276
	$memberEmailWild = array();
2277
	while ($row = $smcFunc['db_fetch_assoc']($request))
2278
	{
2279
		if ($row['id_member'])
2280
			$memberIDs[$row['id_member']] = $row['id_member'];
2281
		if ($row['email_address'])
2282
		{
2283
			// Does it have a wildcard - if so we can't do a IN on it.
2284
			if (strpos($row['email_address'], '%') !== false)
2285
				$memberEmailWild[$row['email_address']] = $row['email_address'];
2286
			else
2287
				$memberEmails[$row['email_address']] = $row['email_address'];
2288
		}
2289
	}
2290
	$smcFunc['db_free_result']($request);
2291
2292
	// Build up the query.
2293
	$queryPart = array();
2294
	$queryValues = array();
2295
	if (!empty($memberIDs))
2296
	{
2297
		$queryPart[] = 'mem.id_member IN ({array_string:member_ids})';
2298
		$queryValues['member_ids'] = $memberIDs;
2299
	}
2300
	if (!empty($memberEmails))
2301
	{
2302
		$queryPart[] = 'mem.email_address IN ({array_string:member_emails})';
2303
		$queryValues['member_emails'] = $memberEmails;
2304
	}
2305
	$count = 0;
2306
	foreach ($memberEmailWild as $email)
2307
	{
2308
		$queryPart[] = 'mem.email_address LIKE {string:wild_' . $count . '}';
2309
		$queryValues['wild_' . $count++] = $email;
0 ignored issues
show
Coding Style introduced by
Increment and decrement operators must be bracketed when used in string concatenation
Loading history...
2310
	}
2311
2312
	// Find all banned members.
2313
	if (!empty($queryPart))
2314
	{
2315
		$request = $smcFunc['db_query']('', '
2316
			SELECT mem.id_member, mem.is_activated
2317
			FROM {db_prefix}members AS mem
2318
			WHERE ' . implode(' OR ', $queryPart),
2319
			$queryValues
2320
		);
2321
		while ($row = $smcFunc['db_fetch_assoc']($request))
2322
		{
2323
			if (!in_array($row['id_member'], $allMembers))
2324
			{
2325
				$allMembers[] = $row['id_member'];
2326
				// Do they need an update?
2327
				if ($row['is_activated'] < 10)
2328
				{
2329
					$updates[($row['is_activated'] + 10)][] = $row['id_member'];
2330
					$newMembers[] = $row['id_member'];
2331
				}
2332
			}
2333
		}
2334
		$smcFunc['db_free_result']($request);
2335
	}
2336
2337
	// We welcome our new members in the realm of the banned.
2338
	if (!empty($newMembers))
2339
		$smcFunc['db_query']('', '
2340
			DELETE FROM {db_prefix}log_online
2341
			WHERE id_member IN ({array_int:new_banned_members})',
2342
			array(
2343
				'new_banned_members' => $newMembers,
2344
			)
2345
		);
2346
2347
	// Find members that are wrongfully marked as banned.
2348
	$request = $smcFunc['db_query']('', '
2349
		SELECT mem.id_member, mem.is_activated - 10 AS new_value
2350
		FROM {db_prefix}members AS mem
2351
			LEFT JOIN {db_prefix}ban_items AS bi ON (bi.id_member = mem.id_member OR mem.email_address LIKE bi.email_address)
2352
			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}))
2353
		WHERE (bi.id_ban IS NULL OR bg.id_ban_group IS NULL)
2354
			AND mem.is_activated >= {int:ban_flag}',
2355
		array(
2356
			'cannot_access_activated' => 1,
2357
			'current_time' => time(),
2358
			'ban_flag' => 10,
2359
		)
2360
	);
2361 View Code Duplication
	while ($row = $smcFunc['db_fetch_assoc']($request))
2362
	{
2363
		// Don't do this twice!
2364
		if (!in_array($row['id_member'], $allMembers))
2365
		{
2366
			$updates[$row['new_value']][] = $row['id_member'];
2367
			$allMembers[] = $row['id_member'];
2368
		}
2369
	}
2370
	$smcFunc['db_free_result']($request);
2371
2372
	if (!empty($updates))
2373
		foreach ($updates as $newStatus => $members)
2374
			updateMemberData($members, array('is_activated' => $newStatus));
2375
2376
	// Update the latest member and our total members as banning may change them.
2377
	updateStats('member');
2378
}
2379
2380
/**
2381
 * Gets basic member data for the ban
2382
 *
2383
 * @param int $id The ID of the member to get data for
2384
 * @return array An aray containing the ID, name, main IP and email address of the specified user
2385
 */
2386
function getMemberData($id)
2387
{
2388
	global $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2389
2390
	$suggestions = array();
2391
	$request = $smcFunc['db_query']('', '
2392
		SELECT id_member, real_name, member_ip, email_address
2393
		FROM {db_prefix}members
2394
		WHERE id_member = {int:current_user}
2395
		LIMIT 1',
2396
		array(
2397
			'current_user' => $id,
2398
		)
2399
	);
2400
	if ($smcFunc['db_num_rows']($request) > 0)
2401
	{
2402
		list ($suggestions['member']['id'], $suggestions['member']['name'], $suggestions['main_ip'], $suggestions['email']) = $smcFunc['db_fetch_row']($request);
2403
		$suggestions['main_ip'] = inet_dtop($suggestions['main_ip']);
2404
	}
2405
	$smcFunc['db_free_result']($request);
2406
2407
	return $suggestions;
2408
}
2409
2410
?>