ManageBans::action_edittrigger()   C
last analyzed

Complexity

Conditions 11
Paths 21

Size

Total Lines 110
Code Lines 61

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 132

Importance

Changes 0
Metric Value
cc 11
eloc 61
c 0
b 0
f 0
nc 21
nop 0
dl 0
loc 110
ccs 0
cts 85
cp 0
crap 132
rs 6.7042

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
 * Controls execution for admin actions in the bans area
5
 *
6
 * @package   ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
9
 *
10
 * @version 2.0 dev
11
 *
12
 */
13
14
namespace ElkArte\AdminController;
15
16
use ElkArte\AbstractController;
17
use ElkArte\Action;
18
use ElkArte\Errors\ErrorContext;
19
use ElkArte\Exceptions\Exception;
20
use ElkArte\Helper\Util;
21
use ElkArte\Languages\Txt;
22
23
/**
24
 * This class controls execution for admin actions in the bans area
25
 * of the admin panel.
26
 *
27
 * @package Bans
28
 */
29
class ManageBans extends AbstractController
30
{
31
	/**
32
	 * Ban center. The main entrance point for all ban center functions.
33
	 *
34
	 * What it does:
35
	 *
36
	 * - It is accessed by ?action=admin;area=ban.
37
	 * - It chooses a function based on the 'sa' parameter, like many others.
38
	 * - The default sub-action is action_list().
39
	 * - It requires the ban_members permission.
40
	 * - It initializes the admin tabs.
41
	 *
42
	 * @event integrate_manage_bans
43
	 * @uses ManageBans template.
44
	 */
45
	public function action_index()
46
	{
47
		global $context, $txt;
48
49
		theme()->getTemplates()->load('ManageBans');
50
		require_once(SUBSDIR . '/Bans.subs.php');
51
52
		$subActions = array(
53
			'add' => array($this, 'action_edit', 'permission' => 'manage_bans'),
54
			'browse' => array($this, 'action_browse', 'permission' => 'manage_bans'),
55
			'edittrigger' => array($this, 'action_edittrigger', 'permission' => 'manage_bans'),
56
			'edit' => array($this, 'action_edit', 'permission' => 'manage_bans'),
57
			'list' => array($this, 'action_list', 'permission' => 'manage_bans'),
58
			'log' => array($this, 'action_log', 'permission' => 'manage_bans'),
59
		);
60
61
		// Start up the controller
62
		$action = new Action('manage_bans');
63
64
		// Default the sub-action to 'view ban list'.
65
		$subAction = $action->initialize($subActions, 'list');
66
67
		// Make the call to integrate-manage_bans
68
		call_integration_hook('integrate_manage_bans', array(&$subActions));
69
70
		// Prepare some items for the template
71
		$context['page_title'] = $txt['ban_title'];
72
		$context['sub_action'] = $subAction;
73
74
		// Tabs for browsing the different ban functions.
75
		$context[$context['admin_menu_name']]['object']->prepareTabData([
76
			'title' => 'ban_title',
77
			'help' => 'ban_members',
78
			'description' => 'ban_description',
79
			'tabs' => [
80
				'list' => [
81
					'description' => $txt['ban_description'],
82
					'href' => getUrl('admin', ['action' => 'admin', 'area' => 'ban', 'sa' => 'list']),
83
					'selected' => $subAction === 'list' || $subAction === 'edit' || $subAction === 'edittrigger',
84
				],
85
				'add' => [
86
					'description' => $txt['ban_description'],
87
					'href' => getUrl('admin', ['action' => 'admin', 'area' => 'ban', 'sa' => 'add']),
88
				],
89
				'browse' => [
90
					'description' => $txt['ban_trigger_browse_description'],
91
					'href' => getUrl('admin', ['action' => 'admin', 'area' => 'ban', 'sa' => 'browse']),
92
				],
93
				'log' => [
94
					'description' => $txt['ban_log_description'],
95
					'href' => getUrl('admin', ['action' => 'admin', 'area' => 'ban', 'sa' => 'log']),
96
					'is_last' => true,
97
				],
98
			],
99
		]);
100
101
		// Call the right function for this sub-action.
102
		$action->dispatch($subAction);
103
	}
104
105
	/**
106
	 * Shows a list of bans currently set.
107
	 *
108
	 * What it does:
109
	 *
110
	 * - It is accessed by ?action=admin;area=ban;sa=list.
111
	 * - It removes expired bans.
112
	 * - It allows sorting on different criteria.
113
	 * - It also handles removal of selected ban items.
114
	 *
115
	 * @uses the main ManageBans template.
116
	 */
117
	public function action_list()
118
	{
119
		global $txt, $context;
120
121
		require_once(SUBSDIR . '/Bans.subs.php');
122
123
		// User pressed the 'remove selection button'.
124
		if (!empty($this->_req->post->removeBans) && !empty($this->_req->post->remove) && is_array($this->_req->post->remove))
125
		{
126
			checkSession();
127
128
			// Make sure every entry is a proper integer.
129
			$to_remove = array_map('intval', $this->_req->post->remove);
130
131
			// Unban them all!
132
			removeBanGroups($to_remove);
133
			removeBanTriggers($to_remove);
134
135
			// No more caching this ban!
136
			updateSettings(array('banLastUpdated' => time()));
137
138
			// Some members might be unbanned now. Update the members table.
139
			updateBanMembers();
140
		}
141
142
		// Create a date string so we don't overload them with date info.
143
		if (preg_match('~%[AaBbCcDdeGghjmuYy](?:[^%]*%[AaBbCcDdeGghjmuYy])*~', $this->user->time_format, $matches) == 0 || empty($matches[0]))
0 ignored issues
show
Bug introduced by
It seems like $this->user->time_format can also be of type null; however, parameter $subject of preg_match() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

143
		if (preg_match('~%[AaBbCcDdeGghjmuYy](?:[^%]*%[AaBbCcDdeGghjmuYy])*~', /** @scrutinizer ignore-type */ $this->user->time_format, $matches) == 0 || empty($matches[0]))
Loading history...
Bug Best Practice introduced by
The property time_format does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
144
		{
145
			$context['ban_time_format'] = $this->user->time_format;
146
		}
147
		else
148
		{
149
			$context['ban_time_format'] = $matches[0];
150
		}
151
152
		// Lets build a nice create list to show them the bans
153
		$listOptions = array(
154
			'id' => 'ban_list',
155
			'title' => $txt['ban_title'],
156
			'items_per_page' => 20,
157
			'base_href' => getUrl('admin', ['action' => 'admin', 'area' => 'ban', 'sa' => 'list']),
158
			'default_sort_col' => 'added',
159
			'default_sort_dir' => 'desc',
160
			'get_items' => array(
161
				'function' => 'list_getBans',
162
			),
163
			'get_count' => array(
164
				'function' => 'list_getNumBans',
165
			),
166
			'no_items_label' => $txt['ban_no_entries'],
167
			'columns' => array(
168
				'name' => array(
169
					'header' => array(
170
						'value' => $txt['ban_name'],
171
					),
172
					'data' => array(
173
						'db' => 'name',
174
					),
175
					'sort' => array(
176
						'default' => 'bg.name',
177
						'reverse' => 'bg.name DESC',
178
					),
179
				),
180
				'notes' => array(
181
					'header' => array(
182
						'value' => $txt['ban_notes'],
183
					),
184
					'data' => array(
185
						'db' => 'notes',
186
						'class' => 'smalltext',
187
					),
188
					'sort' => array(
189
						'default' => 'LENGTH(bg.notes) > 0 DESC, bg.notes',
190
						'reverse' => 'LENGTH(bg.notes) > 0, bg.notes DESC',
191
					),
192
				),
193
				'reason' => array(
194
					'header' => array(
195
						'value' => $txt['ban_reason'],
196
					),
197
					'data' => array(
198
						'db' => 'reason',
199
						'class' => 'smalltext',
200
					),
201
					'sort' => array(
202
						'default' => 'LENGTH(bg.reason) > 0 DESC, bg.reason',
203
						'reverse' => 'LENGTH(bg.reason) > 0, bg.reason DESC',
204
					),
205
				),
206
				'added' => array(
207
					'header' => array(
208
						'value' => $txt['ban_added'],
209
					),
210
					'data' => array(
211
						'function' => static function ($rowData) {
212
							global $context;
213
							return standardTime($rowData['ban_time'], empty($context['ban_time_format']) ? true : $context['ban_time_format']);
214
						},
215
					),
216
					'sort' => array(
217
						'default' => 'bg.ban_time',
218
						'reverse' => 'bg.ban_time DESC',
219
					),
220
				),
221
				'expires' => array(
222
					'header' => array(
223
						'value' => $txt['ban_expires'],
224
					),
225
					'data' => array(
226
						'function' => static function ($rowData) {
227
							global $txt;
228
							// This ban never expires...whahaha.
229
							if ($rowData['expire_time'] === null)
230
							{
231
								return $txt['never'];
232
							}
233
							// This ban has already expired.
234
							if ($rowData['expire_time'] < time())
235
							{
236
								return sprintf('<span class="error">%1$s</span>', $txt['ban_expired']);
237
							}
238
							// Still need to wait a few days for this ban to expire.
239
							else
240
							{
241
								return sprintf('%1$d&nbsp;%2$s', ceil(($rowData['expire_time'] - time()) / (60 * 60 * 24)), $txt['ban_days']);
242
							}
243
						},
244
					),
245
					'sort' => array(
246
						'default' => 'COALESCE(bg.expire_time, 1=1) DESC, bg.expire_time DESC',
247
						'reverse' => 'COALESCE(bg.expire_time, 1=1), bg.expire_time',
248
					),
249
				),
250
				'num_triggers' => array(
251
					'header' => array(
252
						'value' => $txt['ban_triggers'],
253
						'class' => 'centertext',
254
					),
255
					'data' => array(
256
						'db' => 'num_triggers',
257
						'class' => 'centertext'
258
					),
259
					'sort' => array(
260
						'default' => 'num_triggers DESC',
261
						'reverse' => 'num_triggers',
262
					),
263
				),
264
				'actions' => array(
265
					'header' => array(
266
						'value' => $txt['ban_actions'],
267
					),
268
					'data' => array(
269
						'sprintf' => array(
270
							'format' => '<a href="' . getUrl('admin', ['action' => 'admin', 'area' => 'ban', 'sa' => 'edit', 'bg' => '%1$d']) . '">' . $txt['modify'] . '</a>',
271
							'params' => array(
272
								'id_ban_group' => false,
273
							),
274
						),
275
					),
276
				),
277
				'check' => array(
278
					'header' => array(
279
						'value' => '<input type="checkbox" onclick="invertAll(this, this.form);" class="input_check" />',
280
					),
281
					'data' => array(
282
						'sprintf' => array(
283
							'format' => '<input type="checkbox" name="remove[]" value="%1$d" class="input_check" />',
284
							'params' => array(
285
								'id_ban_group' => false,
286
							),
287
						),
288
					),
289
				),
290
			),
291
			'form' => array(
292
				'href' => getUrl('admin', ['action' => 'admin', 'area' => 'ban', 'sa' => 'list']),
293
			),
294
			'additional_rows' => array(
295
				array(
296
					'class' => 'submitbutton flow_flex_additional_row',
297
					'position' => 'below_table_data',
298
					'value' => '<input type="submit" name="removeBans" value="' . $txt['ban_remove_selected'] . '" onclick="return confirm(\'' . $txt['ban_remove_selected_confirm'] . '\');" />',
299
				),
300
			),
301
		);
302
303
		createList($listOptions);
304
	}
305
306
	/**
307
	 * This function is behind the screen for adding new bans and modifying existing ones.
308
	 *
309
	 * Adding new bans:
310
	 *  - is accessed by ?action=admin;area=ban;sa=add.
311
	 *  - uses the ban_edit sub template of the ManageBans template.
312
	 *
313
	 * Modifying existing bans:
314
	 *  - is accessed by ?action=admin;area=ban;sa=edit;bg=x
315
	 *  - uses the ban_edit sub template of the ManageBans template.
316
	 *  - shows a list of ban triggers for the specified ban.
317
	 *
318
	 * @event integrate_list_ban_items
319
	 */
320
	public function action_edit()
321
	{
322
		global $txt, $modSettings, $context;
323
324
		require_once(SUBSDIR . '/Bans.subs.php');
325
326
		$ban_errors = ErrorContext::context('ban', 1);
327
328
		// Saving a new or edited ban?
329
		if ((isset($this->_req->post->add_ban) || isset($this->_req->post->modify_ban) || isset($this->_req->post->remove_selection)) && !$ban_errors->hasErrors())
330
		{
331
			$this->action_edit2();
332
		}
333
334
		$ban_group_id = $context['ban']['id'] ?? $this->_req->getQuery('bg', 'intval', 0);
335
336
		// Template needs this to show errors using javascript
337
		Txt::load('Errors');
338
		createToken('admin-bet');
339
		$context['form_url'] = getUrl('admin', ['action' => 'admin', 'area' => 'ban', 'sa' => 'edit']);
340
341
		// Prepare any errors found to the template to show
342
		$context['ban_errors'] = array(
343
			'errors' => $ban_errors->prepareErrors(),
344
			'type' => $ban_errors->getErrorType() == 0 ? 'minor' : 'serious',
345
			'title' => $txt['ban_errors_detected'],
346
		);
347
348
		if (!$ban_errors->hasErrors())
349
		{
350
			// If we're editing an existing ban, get it from the database.
351
			if (!empty($ban_group_id))
352
			{
353
				$context['ban_group_id'] = $ban_group_id;
354
355
				// Setup for a createlist
356
				$listOptions = array(
357
					'id' => 'ban_items',
358
					'base_href' => getUrl('admin', ['action' => 'admin', 'area' => 'ban', 'sa' => 'edit', 'bg' => $ban_group_id]),
359
					'no_items_label' => $txt['ban_no_triggers'],
360
					'items_per_page' => $modSettings['defaultMaxMessages'],
361
					'get_items' => array(
362
						'function' => 'list_getBanItems',
363
						'params' => array(
364
							'ban_group_id' => $ban_group_id,
365
						),
366
					),
367
					'get_count' => array(
368
						'function' => 'list_getNumBanItems',
369
						'params' => array(
370
							'ban_group_id' => $ban_group_id,
371
						),
372
					),
373
					'columns' => array(
374
						'type' => array(
375
							'header' => array(
376
								'value' => $txt['ban_banned_entity'],
377
								'style' => 'width: 60%;',
378
							),
379
							'data' => array(
380
								'function' => static function ($ban_item) {
381
									global $txt;
382
383
									if (in_array($ban_item['type'], array('ip', 'hostname', 'email')))
384
									{
385
										return '<strong>' . $txt[$ban_item['type']] . ':</strong>&nbsp;' . $ban_item[$ban_item['type']];
386
									}
387
388
									if ($ban_item['type'] === 'user')
389
									{
390
										return '<strong>' . $txt['username'] . ':</strong>&nbsp;' . $ban_item['user']['link'];
391
									}
392
393
									return '<strong>' . $txt['unknown'] . ':</strong>&nbsp;' . $ban_item['no_bantype_selected'];
394
								},
395
							),
396
						),
397
						'hits' => array(
398
							'header' => array(
399
								'value' => $txt['ban_hits'],
400
								'style' => 'width: 15%;text-align: center',
401
							),
402
							'data' => array(
403
								'db' => 'hits',
404
								'class' => 'centertext'
405
							),
406
						),
407
						'id' => array(
408
							'header' => array(
409
								'value' => $txt['ban_actions'],
410
								'style' => 'width: 15%;',
411
							),
412
							'data' => array(
413
								'function' => static function ($ban_item) {
414
									global $txt, $context;
415
									return '<a href="' . getUrl('admin', ['action' => 'admin', 'area' => 'ban', 'sa' => 'edittrigger', 'bg' => $context['ban']['id'], 'bi' => $ban_item['id']]) . '">' . $txt['ban_edit_trigger'] . '</a>';
416
								},
417
							),
418
						),
419
						'checkboxes' => array(
420
							'header' => array(
421
								'value' => '<input type="checkbox" onclick="invertAll(this, this.form, \'ban_items\');" class="input_check" />',
422
								'style' => 'width: 5%;',
423
							),
424
							'data' => array(
425
								'sprintf' => array(
426
									'format' => '<input type="checkbox" name="ban_items[]" value="%1$d" class="input_check" />',
427
									'params' => array(
428
										'id' => false,
429
									),
430
								),
431
							),
432
						),
433
					),
434
					'form' => array(
435
						'href' => getUrl('admin', ['action' => 'admin', 'area' => 'ban', 'sa' => 'edit', 'bg' => $ban_group_id]),
436
					),
437
					'additional_rows' => array(
438
						array(
439
							'position' => 'below_table_data',
440
							'class' => 'submitbutton',
441
							'value' => '
442
								<input type="submit" name="remove_selection" value="' . $txt['ban_remove_selected_triggers'] . '" class="right_submit" />
443
								<a class="linkbutton floatright" href="' . getUrl('admin', ['action' => 'admin', 'area' => 'ban', 'sa' => 'edittrigger', 'bg' => $ban_group_id]) . '">' . $txt['ban_add_trigger'] . '</a>
444
								<input type="hidden" name="bg" value="' . $ban_group_id . '" />
445
								<input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '" />
446
								<input type="hidden" name="' . $context['admin-bet_token_var'] . '" value="' . $context['admin-bet_token'] . '" />',
447
						),
448
					),
449
				);
450
				createList($listOptions);
451
			}
452
			// Not an existing one, then it's probably a new one.
453
			else
454
			{
455
				$context['ban'] = array(
456
					'id' => 0,
457
					'name' => '',
458
					'expiration' => array(
459
						'status' => 'never',
460
						'days' => 0
461
					),
462
					'reason' => '',
463
					'notes' => '',
464
					'ban_days' => 0,
465
					'cannot' => array(
466
						'access' => true,
467
						'post' => false,
468
						'register' => false,
469
						'login' => false,
470
					),
471
					'is_new' => true,
472
				);
473
				$context['ban_suggestions'] = array(
474
					'main_ip' => '',
475
					'hostname' => '',
476
					'email' => '',
477
					'member' => array(
478
						'id' => 0,
479
					),
480
				);
481
482
				// Overwrite some of the default form values if a user ID was given.
483
				if (!empty($this->_req->query->u))
484
				{
485
					$context['ban_suggestions'] = array_merge($context['ban_suggestions'], getMemberData((int) $this->_req->query->u));
486
487
					if (!empty($context['ban_suggestions']['member']['id']))
488
					{
489
						$context['ban_suggestions']['href'] = getUrl('profile', ['action' => 'profile', 'u' => $context['ban_suggestions']['member']['id'], 'name' => $context['ban_suggestions']['member']['name']]);
490
						$context['ban_suggestions']['member']['link'] = '<a href="' . $context['ban_suggestions']['href'] . '">' . $context['ban_suggestions']['member']['name'] . '</a>';
491
492
						// Default the ban name to the name of the banned member.
493
						$context['ban']['name'] = $context['ban_suggestions']['member']['name'];
494
495
						// @todo: there should be a better solution...
496
						// used to lock the "Ban on Username" input when banning from profile
497
						$context['ban']['from_user'] = true;
498
499
						// Would be nice if we could also ban the hostname.
500
						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']))
501
						{
502
							$context['ban_suggestions']['hostname'] = host_from_ip($context['ban_suggestions']['main_ip']);
503
						}
504
505
						$context['ban_suggestions']['other_ips'] = banLoadAdditionalIPs($context['ban_suggestions']['member']['id']);
506
					}
507
				}
508
				else
509
				{
510
					$context['use_autosuggest'] = true;
511
					loadJavascriptFile('suggest.js', array('defer' => true));
512
				}
513
			}
514
		}
515
516
		// Set the right template
517
		$context['sub_template'] = 'ban_edit';
518
519
		// A couple of text strings we *may* need
520
		theme()->addJavascriptVar(array(
521
			'txt_ban_name_empty' => $txt['ban_name_empty'],
522
			'txt_ban_restriction_empty' => $txt['ban_restriction_empty']), true
523
		);
524
525
		// And a bit of javascript to enable/disable some fields
526
		theme()->addInlineJavascript('fUpdateStatus();', true);
527
	}
528
529
	/**
530
	 * This function handles submitted forms that add, modify or remove ban triggers.
531
	 */
532
	public function action_edit2()
533
	{
534
		global $context;
535
536
		require_once(SUBSDIR . '/Bans.subs.php');
537
538
		// Check with security first
539
		checkSession();
540
		validateToken('admin-bet');
541
542
		$ban_errors = ErrorContext::context('ban', 1);
543
544
		// Adding or editing a ban group
545
		if (isset($this->_req->post->add_ban) || isset($this->_req->post->modify_ban))
546
		{
547
			$ban_info = array();
548
549
			// Let's collect all the information we need
550
			$ban_info['id'] = $this->_req->getQuery('bg', 'intval', 0);
551
			if (empty($ban_info['id']))
552
			{
553
				$ban_info['id'] = $this->_req->getPost('bg', 'intval', 0);
554
			}
555
556
			$ban_info['is_new'] = empty($ban_info['id']);
557
			$ban_info['expire_date'] = $this->_req->getPost('expire_date', 'intval', 0);
558
			$ban_info['expiration'] = array(
559
				'status' => isset($this->_req->post->expiration) && in_array($this->_req->post->expiration, array('never', 'one_day', 'expired')) ? $this->_req->post->expiration : 'never',
560
				'days' => $ban_info['expire_date'],
561
			);
562
			$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);
563
			$ban_info['full_ban'] = empty($this->_req->post->full_ban) ? 0 : 1;
564
			$ban_info['reason'] = $this->_req->getPost('reason', '\\ElkArte\\Helper\\Util::htmlspecialchars[ENT_QUOTES]', '');
565
			$ban_info['name'] = $this->_req->getPost('ban_name', '\\ElkArte\\Helper\\Util::htmlspecialchars[ENT_QUOTES]', '');
566
			$ban_info['notes'] = $this->_req->getPost('notes', '\\ElkArte\\Helper\\Util::htmlspecialchars[ENT_QUOTES]', '');
567
			$ban_info['notes'] = str_replace(array("\r", "\n", '  '), array('', '<br />', '&nbsp; '), $ban_info['notes']);
568
			$ban_info['cannot']['access'] = empty($ban_info['full_ban']) ? 0 : 1;
569
			$ban_info['cannot']['post'] = !empty($ban_info['full_ban']) || empty($this->_req->post->cannot_post) ? 0 : 1;
570
			$ban_info['cannot']['register'] = !empty($ban_info['full_ban']) || empty($this->_req->post->cannot_register) ? 0 : 1;
571
			$ban_info['cannot']['login'] = !empty($ban_info['full_ban']) || empty($this->_req->post->cannot_login) ? 0 : 1;
572
573
			$ban_group_id = empty($ban_info['id']) ? insertBanGroup($ban_info) : updateBanGroup($ban_info);
574
575
			if ($ban_group_id !== false)
576
			{
577
				$ban_info['id'] = $ban_group_id;
578
				$ban_info['is_new'] = false;
579
			}
580
581
			$context['ban'] = $ban_info;
582
		}
583
584
		// Update the triggers associated with this ban
585
		if (isset($this->_req->post->ban_suggestions, $ban_info['id']))
586
		{
587
			$saved_triggers = saveTriggers((array) $this->_req->post, $ban_info['id'], $this->_req->getQuery('u', 'intval', 0), $this->_req->getQuery('bi', 'intval', 0));
588
			$context['ban_suggestions']['saved_triggers'] = $saved_triggers;
589
		}
590
591
		// Something went wrong somewhere, ban info or triggers, ... Oh well, let's go back.
592
		if ($ban_errors->hasErrors())
593
		{
594
			$context['ban_suggestions'] = empty($saved_triggers) ? '' : $saved_triggers;
595
			$context['ban']['from_user'] = true;
596
597
			// They may have entered a name not using the member select box
598
			if (isset($this->_req->query->u))
599
			{
600
				$context['ban_suggestions'] = array_merge($context['ban_suggestions'], getMemberData((int) $this->_req->query->u));
601
			}
602
			elseif (isset($this->_req->query->user))
603
			{
604
				$context['ban']['from_user'] = false;
605
				$context['use_autosuggest'] = true;
606
				$context['ban_suggestions']['member']['name'] = $this->_req->getQuery('user', 'trim|strval', '');
607
			}
608
609
			// Not strictly necessary, but it's nice
610
			if (!empty($context['ban_suggestions']['member']['id']))
611
			{
612
				$context['ban_suggestions']['other_ips'] = banLoadAdditionalIPs($context['ban_suggestions']['member']['id']);
613
			}
614
615
			return $this->action_edit();
616
		}
617
618
		if (isset($this->_req->post->ban_items))
619
		{
620
			$ban_group_id = $this->_req->getQuery('bg', 'intval', 0);
621
			$ban_items = array_map('intval', $this->_req->post->ban_items);
622
623
			removeBanTriggers($ban_items, $ban_group_id);
624
		}
625
626
		// Register the last modified date.
627
		updateSettings(array('banLastUpdated' => time()));
628
629
		// Update the member table to represent the new ban situation.
630
		updateBanMembers();
631
632
		// Go back to an appropriate spot
633
		redirectexit('action=admin;area=ban;sa=' . (isset($this->_req->post->add_ban) ? 'list' : 'edit;bg=' . ($ban_group_id ?? 0)));
634
	}
635
636
	/**
637
	 * This handles the listing of ban log entries, and allows their deletion.
638
	 *
639
	 * What it does:
640
	 *
641
	 * - Shows a list of logged access attempts by banned users.
642
	 * - It is accessed by ?action=admin;area=ban;sa=log.
643
	 * - allows sorting of several columns.
644
	 * - also handles deletion of (a selection of) log entries.
645
	 */
646
	public function action_log()
647
	{
648
		global $context, $txt;
649
650
		require_once(SUBSDIR . '/Bans.subs.php');
651
652
		// Delete one or more entries.
653
		if (!empty($this->_req->post->removeAll)
654
			|| (!empty($this->_req->post->removeSelected) && !empty($this->_req->post->remove)))
655
		{
656
			checkSession();
657
			validateToken('admin-bl');
658
659
			// 'Delete all entries' button was pressed.
660
			if (!empty($this->_req->post->removeAll))
661
			{
662
				removeBanLogs();
663
			}
664
			// 'Delete selection' button was pressed.
665
			else
666
			{
667
				$to_remove = array_map('intval', $this->_req->post->remove);
668
				removeBanLogs($to_remove);
669
			}
670
		}
671
672
		// Build a nice log list for viewing
673
		$listOptions = array(
674
			'id' => 'ban_log',
675
			'title' => $txt['ban_log'],
676
			'items_per_page' => 30,
677
			'base_href' => $context['admin_area'] === 'ban' ? getUrl('admin', ['action' => 'admin', 'area' => 'ban', 'sa' => 'log']) : getUrl('admin', ['action' => 'admin', 'area' => 'logs', 'sa' => 'banlog']),
678
			'default_sort_col' => 'date',
679
			'get_items' => array(
680
				'function' => 'list_getBanLogEntries',
681
			),
682
			'get_count' => array(
683
				'function' => 'list_getNumBanLogEntries',
684
			),
685
			'no_items_label' => $txt['ban_log_no_entries'],
686
			'columns' => array(
687
				'ip' => array(
688
					'header' => array(
689
						'value' => $txt['ban_log_ip'],
690
					),
691
					'data' => array(
692
						'sprintf' => array(
693
							'format' => '<a href="' . getUrl('admin', ['action' => 'trackip', 'searchip' => '%1$s']) . '">%1$s</a>',
694
							'params' => array(
695
								'ip' => false,
696
							),
697
						),
698
					),
699
					'sort' => array(
700
						'default' => 'lb.ip',
701
						'reverse' => 'lb.ip DESC',
702
					),
703
				),
704
				'email' => array(
705
					'header' => array(
706
						'value' => $txt['ban_log_email'],
707
					),
708
					'data' => array(
709
						'db_htmlsafe' => 'email',
710
					),
711
					'sort' => array(
712
						'default' => "lb.email = '', lb.email",
713
						'reverse' => "lb.email != '', lb.email DESC",
714
					),
715
				),
716
				'member' => array(
717
					'header' => array(
718
						'value' => $txt['ban_log_member'],
719
					),
720
					'data' => array(
721
						'sprintf' => array(
722
							'format' => '<a href="' . getUrl('profile', ['action' => 'profile', 'u' => '%1$d', 'name' => '%2$s']) . '">%2$s</a>',
723
							'params' => array(
724
								'id_member' => false,
725
								'real_name' => false,
726
							),
727
						),
728
					),
729
					'sort' => array(
730
						'default' => 'COALESCE(mem.real_name, 1=1), mem.real_name',
731
						'reverse' => 'COALESCE(mem.real_name, 1=1) DESC, mem.real_name DESC',
732
					),
733
				),
734
				'date' => array(
735
					'header' => array(
736
						'value' => $txt['ban_log_date'],
737
					),
738
					'data' => array(
739
						'function' => static fn($rowData) => standardTime($rowData['log_time']),
740
					),
741
					'sort' => array(
742
						'default' => 'lb.log_time DESC',
743
						'reverse' => 'lb.log_time',
744
					),
745
				),
746
				'check' => array(
747
					'header' => array(
748
						'value' => '<input type="checkbox" onclick="invertAll(this, this.form);" class="input_check" />',
749
						'class' => 'centertext',
750
					),
751
					'data' => array(
752
						'sprintf' => array(
753
							'format' => '<input type="checkbox" name="remove[]" value="%1$d" class="input_check" />',
754
							'params' => array(
755
								'id_ban_log' => false,
756
							),
757
						),
758
						'class' => 'centertext',
759
					),
760
				),
761
			),
762
			'form' => array(
763
				'href' => $context['admin_area'] === 'ban' ? getUrl('admin', ['action' => 'admin', 'area' => 'ban', 'sa' => 'log']) : getUrl('admin', ['action' => 'admin', 'area' => 'logs', 'sa' => 'banlog']),
764
				'include_start' => true,
765
				'include_sort' => true,
766
				'token' => 'admin-bl',
767
			),
768
			'additional_rows' => array(
769
				array(
770
					'class' => 'submitbutton',
771
					'position' => 'bottom_of_list',
772
					'value' => '
773
						<input type="submit" name="removeSelected" value="' . $txt['ban_log_remove_selected'] . '" onclick="return confirm(\'' . $txt['ban_log_remove_selected_confirm'] . '\');" />
774
						<input type="submit" name="removeAll" value="' . $txt['ban_log_remove_all'] . '" onclick="return confirm(\'' . $txt['ban_log_remove_all_confirm'] . '\');" class="right_submit" />',
775
				),
776
			),
777
		);
778
779
		createToken('admin-bl');
780
781
		// Build the list
782
		createList($listOptions);
783
784
		$context['page_title'] = $txt['ban_log'];
785
	}
786
787
	/**
788
	 * This function handles the ins and outs of the screen for adding new ban
789
	 * triggers or modifying existing ones.
790
	 *
791
	 * - Adding new ban triggers:
792
	 *   - is accessed by ?action=admin;area=ban;sa=edittrigger;bg=x
793
	 *   - uses the ban_edit_trigger sub template of ManageBans.
794
	 *
795
	 * - Editing existing ban triggers:
796
	 *   - is accessed by ?action=admin;area=ban;sa=edittrigger;bg=x;bi=y
797
	 *   - uses the ban_edit_trigger sub template of ManageBans.
798
	 *
799
	 * @uses sub template ban_edit_trigger
800
	 */
801
	public function action_edittrigger()
802
	{
803
		global $context;
804
805
		require_once(SUBSDIR . '/Bans.subs.php');
806
807
		$ban_group = $this->_req->get('bg', 'intval', 0);
808
		$ban_id = $this->_req->get('bi', 'intval', 0);
809
810
		if (empty($ban_group))
811
		{
812
			throw new Exception('ban_not_found', false);
813
		}
814
815
		// Adding a new trigger
816
		if (isset($this->_req->post->add_new_trigger) && !empty($this->_req->post->ban_suggestions))
817
		{
818
			saveTriggers((array) $this->_req->post, $ban_group, 0, $ban_id);
819
			redirectexit('action=admin;area=ban;sa=edit;bg=' . $ban_group);
820
		}
821
		// Edit an existing trigger with new / updated details
822
		elseif (isset($this->_req->post->edit_trigger) && !empty($this->_req->post->ban_suggestions))
823
		{
824
			// The first replaces the old one, the others are added new
825
			// (simplification, otherwise it would require another query and some work...)
826
			$dummy = (array) $this->_req->post;
827
			$dummy['ban_suggestions'] = (array) array_shift($this->_req->post->ban_suggestions);
828
			saveTriggers($dummy, $ban_group, 0, $ban_id);
829
			if ($this->_req->post->ban_suggestions !== [])
830
			{
831
				saveTriggers((array) $this->_req->post, $ban_group);
832
			}
833
834
			redirectexit('action=admin;area=ban;sa=edit;bg=' . $ban_group);
835
		}
836
		// Removing a ban trigger by clearing the checkbox
837
		elseif (isset($this->_req->post->edit_trigger))
838
		{
839
			removeBanTriggers($ban_id);
840
			redirectexit('action=admin;area=ban;sa=edit;bg=' . $ban_group);
841
		}
842
843
		// No id supplied, this must be a new trigger being added
844
		if (empty($ban_id))
845
		{
846
			$context['ban_trigger'] = array(
847
				'id' => 0,
848
				'group' => $ban_group,
849
				'ip' => array(
850
					'value' => '',
851
					'selected' => true,
852
				),
853
				'hostname' => array(
854
					'selected' => false,
855
					'value' => '',
856
				),
857
				'email' => array(
858
					'value' => '',
859
					'selected' => false,
860
				),
861
				'banneduser' => array(
862
					'value' => '',
863
					'selected' => false,
864
				),
865
				'is_new' => true,
866
			);
867
		}
868
		// Otherwise its an existing trigger they want to edit
869
		else
870
		{
871
			$ban_row = banDetails($ban_id, $ban_group);
872
			if (empty($ban_row))
873
			{
874
				throw new Exception('ban_not_found', false);
875
			}
876
877
			$row = $ban_row[$ban_id];
878
879
			// Load it up for the template
880
			$context['ban_trigger'] = array(
881
				'id' => $row['id_ban'],
882
				'group' => $row['id_ban_group'],
883
				'ip' => array(
884
					'value' => empty($row['ip_low1']) ? '' : range2ip(array($row['ip_low1'], $row['ip_low2'], $row['ip_low3'], $row['ip_low4'], $row['ip_low5'], $row['ip_low6'], $row['ip_low7'], $row['ip_low8']), array($row['ip_high1'], $row['ip_high2'], $row['ip_high3'], $row['ip_high4'], $row['ip_high5'], $row['ip_high6'], $row['ip_high7'], $row['ip_high8'])),
885
					'selected' => !empty($row['ip_low1']),
886
				),
887
				'hostname' => array(
888
					'value' => str_replace('%', '*', $row['hostname']),
889
					'selected' => !empty($row['hostname']),
890
				),
891
				'email' => array(
892
					'value' => str_replace('%', '*', $row['email_address']),
893
					'selected' => !empty($row['email_address'])
894
				),
895
				'banneduser' => array(
896
					'value' => $row['member_name'],
897
					'selected' => !empty($row['member_name'])
898
				),
899
				'is_new' => false,
900
			);
901
		}
902
903
		// The template uses the autosuggest functions
904
		loadJavascriptFile('suggest.js', array('defer' => true));
905
906
		// Template we will use
907
		$context['sub_template'] = 'ban_edit_trigger';
908
		$context['form_url'] = getUrl('admin', ['action' => 'admin', 'area' => 'ban', 'sa' => 'edittrigger']);
909
910
		createToken('admin-bet');
911
	}
912
913
	/**
914
	 * This handles the screen for showing the banned entities
915
	 *
916
	 * What it does:
917
	 *
918
	 * - It is accessed by ?action=admin;area=ban;sa=browse
919
	 * - It uses sub-tabs for browsing by IP, hostname, email or username.
920
	 *
921
	 * @uses ManageBans template, browse_triggers sub template.
922
	 */
923
	public function action_browse()
924
	{
925
		global $modSettings, $context, $txt;
926
927
		require_once(SUBSDIR . '/Bans.subs.php');
928
929
		if (!empty($this->_req->post->remove_triggers) && !empty($this->_req->post->remove) && is_array($this->_req->post->remove))
930
		{
931
			checkSession();
932
933
			// Make sure every entry is a proper integer.
934
			$to_remove = array_map('intval', $this->_req->post->remove);
935
936
			removeBanTriggers($to_remove);
937
938
			// Rehabilitate some members.
939
			if ($this->_req->query->entity === 'member')
940
			{
941
				updateBanMembers();
942
			}
943
944
			// Make sure the ban cache is refreshed.
945
			updateSettings(array('banLastUpdated' => time()));
946
		}
947
948
		$context['selected_entity'] = isset($this->_req->query->entity) && in_array($this->_req->query->entity, array('ip', 'hostname', 'email', 'member')) ? $this->_req->query->entity : 'ip';
949
950
		$listOptions = array(
951
			'id' => 'ban_trigger_list',
952
			'title' => $txt['ban_trigger_browse'],
953
			'items_per_page' => $modSettings['defaultMaxMessages'],
954
			'base_href' => getUrl('admin', ['action' => 'admin', 'area' => 'ban', 'sa' => 'browse', 'entity' => $context['selected_entity']]),
955
			'default_sort_col' => 'banned_entity',
956
			'no_items_label' => $txt['ban_no_triggers'],
957
			'get_items' => array(
958
				'function' => 'list_getBanTriggers',
959
				'params' => array(
960
					$context['selected_entity'],
961
				),
962
			),
963
			'get_count' => array(
964
				'function' => 'list_getNumBanTriggers',
965
				'params' => array(
966
					$context['selected_entity'],
967
				),
968
			),
969
			'columns' => array(
970
				'banned_entity' => array(
971
					'header' => array(
972
						'value' => $txt['ban_banned_entity'],
973
					),
974
				),
975
				'ban_name' => array(
976
					'header' => array(
977
						'value' => $txt['ban_name'],
978
					),
979
					'data' => array(
980
						'sprintf' => array(
981
							'format' => '<a href="' . getUrl('admin', ['action' => 'admin', 'area' => 'ban', 'sa' => 'edit', 'bg' => '%1$d']) . '">%2$s</a>',
982
							'params' => array(
983
								'id_ban_group' => false,
984
								'name' => false,
985
							),
986
						),
987
					),
988
					'sort' => array(
989
						'default' => 'bg.name',
990
						'reverse' => 'bg.name DESC',
991
					),
992
				),
993
				'hits' => array(
994
					'header' => array(
995
						'value' => $txt['ban_hits'],
996
					),
997
					'data' => array(
998
						'db' => 'hits',
999
					),
1000
					'sort' => array(
1001
						'default' => 'bi.hits DESC',
1002
						'reverse' => 'bi.hits',
1003
					),
1004
				),
1005
				'check' => array(
1006
					'header' => array(
1007
						'value' => '<input type="checkbox" onclick="invertAll(this, this.form);" class="input_check" />',
1008
						'class' => 'centertext',
1009
					),
1010
					'data' => array(
1011
						'sprintf' => array(
1012
							'format' => '<input type="checkbox" name="remove[]" value="%1$d" class="input_check" />',
1013
							'params' => array(
1014
								'id_ban' => false,
1015
							),
1016
						),
1017
						'class' => 'centertext',
1018
					),
1019
				),
1020
			),
1021
			'form' => array(
1022
				'href' => getUrl('admin', ['action' => 'admin', 'area' => 'ban', 'sa' => 'browse', 'entity' => $context['selected_entity']]),
1023
				'include_start' => true,
1024
				'include_sort' => true,
1025
			),
1026
			'additional_rows' => array(
1027
				array(
1028
					'class' => 'submitbutton flow_flex_additional_row',
1029
					'position' => 'below_table_data',
1030
					'value' => '<input type="submit" name="remove_triggers" value="' . $txt['ban_remove_selected_triggers'] . '" onclick="return confirm(\'' . $txt['ban_remove_selected_triggers_confirm'] . '\');" />',
1031
				),
1032
			),
1033
			'list_menu' => array(
1034
				'show_on' => 'top',
1035
				'class' => 'flow_flex_right',
1036
				'links' => array(
1037
					array(
1038
						'href' => getUrl('admin', ['action' => 'admin', 'area' => 'ban', 'sa' => 'browse', 'entity' => 'ip']),
1039
						'is_selected' => $context['selected_entity'] === 'ip',
1040
						'label' => $txt['ip']
1041
					),
1042
					array(
1043
						'href' => getUrl('admin', ['action' => 'admin', 'area' => 'ban', 'sa' => 'browse', 'entity' => 'hostname']),
1044
						'is_selected' => $context['selected_entity'] === 'hostname',
1045
						'label' => $txt['hostname']
1046
					),
1047
					array(
1048
						'href' => getUrl('admin', ['action' => 'admin', 'area' => 'ban', 'sa' => 'browse', 'entity' => 'email']),
1049
						'is_selected' => $context['selected_entity'] === 'email',
1050
						'label' => $txt['email']
1051
					),
1052
					array(
1053
						'href' => getUrl('admin', ['action' => 'admin', 'area' => 'ban', 'sa' => 'browse', 'entity' => 'member']),
1054
						'is_selected' => $context['selected_entity'] === 'member',
1055
						'label' => $txt['username']
1056
					)
1057
				),
1058
			),
1059
		);
1060
1061
		// Specific data for the first column depending on the selected entity.
1062
		if ($context['selected_entity'] === 'ip')
1063
		{
1064
			$listOptions['columns']['banned_entity']['data'] = array(
1065
				'function' => static fn($rowData) => range2ip(array(
1066
					$rowData['ip_low1'],
1067
					$rowData['ip_low2'],
1068
					$rowData['ip_low3'],
1069
					$rowData['ip_low4'],
1070
					$rowData['ip_low5'],
1071
					$rowData['ip_low6'],
1072
					$rowData['ip_low7'],
1073
					$rowData['ip_low8']
1074
				), array(
1075
					$rowData['ip_high1'],
1076
					$rowData['ip_high2'],
1077
					$rowData['ip_high3'],
1078
					$rowData['ip_high4'],
1079
					$rowData['ip_high5'],
1080
					$rowData['ip_high6'],
1081
					$rowData['ip_high7'],
1082
					$rowData['ip_high8']
1083
				)),
1084
			);
1085
			$listOptions['columns']['banned_entity']['sort'] = array(
1086
				'default' => 'bi.ip_low1, bi.ip_high1, bi.ip_low2, bi.ip_high2, bi.ip_low3, bi.ip_high3, bi.ip_low4, bi.ip_high4, bi.ip_low5, bi.ip_high5, bi.ip_low6, bi.ip_high6, bi.ip_low7, bi.ip_high7, bi.ip_low8, bi.ip_high8',
1087
				'reverse' => 'bi.ip_low1 DESC, bi.ip_high1 DESC, bi.ip_low2 DESC, bi.ip_high2 DESC, bi.ip_low3 DESC, bi.ip_high3 DESC, bi.ip_low4 DESC, bi.ip_high4 DESC, bi.ip_low5 DESC, bi.ip_high5 DESC, bi.ip_low6 DESC, bi.ip_high6 DESC, bi.ip_low7 DESC, bi.ip_high7 DESC, bi.ip_low8 DESC, bi.ip_high8 DESC',
1088
			);
1089
		}
1090
		elseif ($context['selected_entity'] === 'hostname')
1091
		{
1092
			$listOptions['columns']['banned_entity']['data'] = array(
1093
				'function' => static fn($rowData) => strtr(Util::htmlspecialchars($rowData['hostname']), array('%' => '*')),
1094
			);
1095
			$listOptions['columns']['banned_entity']['sort'] = array(
1096
				'default' => 'bi.hostname',
1097
				'reverse' => 'bi.hostname DESC',
1098
			);
1099
		}
1100
		elseif ($context['selected_entity'] === 'email')
1101
		{
1102
			$listOptions['columns']['banned_entity']['data'] = array(
1103
				'function' => static fn($rowData) => strtr(Util::htmlspecialchars($rowData['email_address']), array('%' => '*')),
1104
			);
1105
			$listOptions['columns']['banned_entity']['sort'] = array(
1106
				'default' => 'bi.email_address',
1107
				'reverse' => 'bi.email_address DESC',
1108
			);
1109
		}
1110
		elseif ($context['selected_entity'] === 'member')
1111
		{
1112
			$listOptions['columns']['banned_entity']['data'] = array(
1113
				'sprintf' => array(
1114
					'format' => '<a href="' . getUrl('profile', ['action' => 'profile', 'u' => '%1$d']) . '">%2$s</a>',
1115
					'params' => array(
1116
						'id_member' => false,
1117
						'real_name' => false,
1118
					),
1119
				),
1120
			);
1121
			$listOptions['columns']['banned_entity']['sort'] = array(
1122
				'default' => 'mem.real_name',
1123
				'reverse' => 'mem.real_name DESC',
1124
			);
1125
		}
1126
1127
		// Create the list.
1128
		createList($listOptions);
1129
	}
1130
}
1131