Refactor code for more manageable classes
jdarwood007

jdarwood007 commited on 2023-04-13 17:22:42
Showing 11 changed files, with 2750 additions and 3257 deletions.


Fixes #12
Fixes #13
Fixes #14
Fixes #15
... ...
@@ -1,1322 +0,0 @@
1
-<?php
2
-
3
-/**
4
- * The Admin class for Stop Forum Spam
5
- * @package StopForumSpam
6
- * @author SleePy <sleepy @ simplemachines (dot) org>
7
- * @copyright 2019
8
- * @license 3-Clause BSD https://opensource.org/licenses/BSD-3-Clause
9
- * @version 1.0.1
10
- */
11
-class SFSA
12
-{
13
-	public static $SFSAclass = null;
14
-	private $SFSclass = null;
15
-
16
-	/**
17
-	 * @var string URLS we need to SFS for UI presentation.
18
-	 */
19
-	private $urlSFSipCheck = 'https://www.stopforumspam.com/ipcheck/%1$s';
20
-	private $urlSFSsearch = 'https://www.stopforumspam.com/search/%1$s';
21
-
22
-	/**
23
-	 * @var string The URL for the admin page.
24
-	 */
25
-	private $adminPageURL = null;
26
-	private $adminLogURL = null;
27
-	private $adminTestURL = null;
28
-
29
-	/**
30
-	 * @var mixed Search area handling.
31
-	 */
32
-	private $search_types = array();
33
-	private $search_params = array();
34
-	private $search_params_column = '';
35
-	private $search_params_string = null;
36
-	private $search_params_type = null;
37
-	private $canDeleteLogs = false;
38
-	private $logSearch = array();
39
-
40
-	/**
41
-	 * @var int How long we disable removing logs.
42
-	 */
43
-	private $hoursDisabled = 24;
44
-
45
-	/**
46
-	 * Creates a self reference to the ASL class for use later.
47
-	 *
48
-	 * @version 1.0
49
-	 * @since 1.0
50
-	 * @return object The SFS Admin class is returned.
51
-	 */
52
-	public static function selfClass()
53
-	{
54
-		global $smcFunc;
55
-
56
-		if (is_null(self::$SFSAclass))
57
-		{
58
-			if (!empty($smcFunc['SFSA']))
59
-				self::$SFSAclass = $smcFunc['SFSA'];
60
-			else
61
-			{
62
-				self::$SFSAclass = new SFSA();
63
-				$smcFunc['SFSA'] = self::$SFSAclass;
64
-			}
65
-		}
66
-
67
-		return self::$SFSAclass;
68
-	}
69
-
70
-	/**
71
-	 * Build the class, figure out what software/version we have.
72
-	 * Loads up the defaults.
73
-	 *
74
-	 * @CalledIn SMF 2.0, SMF 2.1
75
-	 * @version 1.0
76
-	 * @since 1.0
77
-	 * @return void No return is generated
78
-	 */
79
-	public function __construct()
80
-	{
81
-		global $smcFunc;
82
-	
83
-		$this->SFSclass = &$smcFunc['classSFS'];
84
-	}
85
-
86
-	/**
87
-	 * Creates the hook to the class for the admin areas.
88
-	 *
89
-	 * @param array $admin_areas A associate array from the software with all valid admin areas.
90
-	 *
91
-	 * @api
92
-	 * @CalledIn SMF 2.0, SMF 2.1
93
-	 * @see SFSA::setupAdminAreas()
94
-	 * @version 1.0
95
-	 * @since 1.0
96
-	 * @uses integrate__admin_areas - Hook SMF2.0
97
-	 * @uses integrate__admin_areas - Hook SMF2.1
98
-	 * @return void No return is generated
99
-	 */
100
-	public static function hook_admin_areas(array &$admin_areas)
101
-	{
102
-		return self::selfClass()->setupAdminAreas($admin_areas);
103
-	}
104
-
105
-	/**
106
-	 * Startup the Admin Panels Additions.
107
-	 * Where things appear are based on what software/version you have.
108
-	 *
109
-	 * @param array $admin_areas A associate array from the software with all valid admin areas.
110
-	 *
111
-	 * @internal
112
-	 * @CalledIn SMF 2.0, SMF 2.1
113
-	 * @version 1.4.0
114
-	 * @since 1.0
115
-	 * @uses integrate__admin_areas - Hook SMF2.0
116
-	 * @uses integrate__admin_areas - Hook SMF2.1
117
-	 * @return void No return is generated
118
-	 */
119
-	private function setupAdminAreas(array &$admin_areas): void
120
-	{
121
-		global $scripturl;
122
-
123
-		// Add the menu item.
124
-		if ($this->SFSclass->versionCheck('2.0', 'smf'))
125
-		{
126
-			$this->adminPageURL = $scripturl . '?action=admin;area=modsettings;sa=sfs';
127
-			$this->adminLogURL = $scripturl . '?action=admin;area=modsettings;sa=sfslog';
128
-			$this->adminTestURL = $scripturl . '?action=admin;area=modsettings;sa=sfstest';
129
-
130
-			$admin_areas['config']['areas']['modsettings']['subsections']['sfs'] = array(
131
-				$this->SFSclass->txt('sfs_admin_area')
132
-			);
133
-			$admin_areas['config']['areas']['modsettings']['subsections']['sfslog'] = array(
134
-				$this->SFSclass->txt('sfs_admin_logs')
135
-			);
136
-			$admin_areas['config']['areas']['modsettings']['subsections']['sfstest'] = array(
137
-				$this->SFSclass->txt('sfs_admin_test')
138
-			);
139
-		}
140
-		else
141
-		{
142
-			$this->adminPageURL = $scripturl . '?action=admin;area=modsettings;sa=sfs';
143
-			$this->adminLogURL = $scripturl . '?action=admin;area=logs;sa=sfslog';
144
-			$this->adminTestURL = $scripturl . '?action=admin;area=regcenter;sa=sfstest';
145
-
146
-			$admin_areas['config']['areas']['modsettings']['subsections']['sfs'] = array(
147
-				$this->SFSclass->txt('sfs_admin_area')
148
-			);
149
-			$admin_areas['members']['areas']['regcenter']['subsections']['sfstest'] = array(
150
-				$this->SFSclass->txt('sfs_admin_test')
151
-			);
152
-		}
153
-	}
154
-
155
-	/**
156
-	 * Setup the Modification's setup page.
157
-	 * For some versions, we put the logs into the modifications sections, its easier.
158
-	 *
159
-	 * @param array $subActions A associate array from the software with all valid modification sections.
160
-	 *
161
-	 * @api
162
-	 * @CalledIn SMF 2.0, SMF 2.1
163
-	 * @see SFSA::setupModifyModifications()
164
-	 * @version 1.0
165
-	 * @since 1.0
166
-	 * @uses integrate_modify_modifications - Hook SMF2.0
167
-	 * @uses integrate_modify_modifications - Hook SMF2.1
168
-	 * @return void No return is generated
169
-	 */
170
-	public static function hook_modify_modifications(array &$subActions)
171
-	{
172
-		return self::selfClass()->setupModifyModifications($subActions);
173
-	}
174
-
175
-	/**
176
-	 * Setup the Modifications section links.
177
-	 * For some versions we add the logs here as well.
178
-	 *
179
-	 * @param array $subActions A associate array from the software with all valid modification sections.
180
-	 *
181
-	 * @internal
182
-	 * @CalledIn SMF 2.0, SMF 2.1
183
-	 * @version 1.4.0
184
-	 * @since 1.0
185
-	 * @uses integrate_modify_modifications - Hook SMF2.0
186
-	 * @uses integrate_modify_modifications - Hook SMF2.1
187
-	 * @return void No return is generated
188
-	 */
189
-	private function setupModifyModifications(array &$subActions): void
190
-	{
191
-		$subActions['sfs'] = 'SFSA::startupAdminConfiguration';
192
-
193
-		// Only in SMF 2.0 do we drop logs here.
194
-		if ($this->SFSclass->versionCheck('2.0', 'smf'))
195
-		{
196
-			$subActions['sfslog'] = 'SFSA::startupLogs';
197
-			$subActions['sfstest'] = 'SFSA::startupTest';
198
-		}
199
-	}
200
-
201
-	/**
202
-	 * The configuration caller.
203
-	 *
204
-	 * @param bool $return_config If true, returns the configuration options for searches.
205
-	 *
206
-	 * @api
207
-	 * @CalledIn SMF 2.0, SMF 2.1
208
-	 * @see SFSA::setupSFSConfiguration
209
-	 * @version 1.0
210
-	 * @since 1.0
211
-	 * @uses integrate_modify_modifications - Hook SMF2.0
212
-	 * @uses integrate_modify_modifications - Hook SMF2.1
213
-	 * @return void No return is generated
214
-	 */
215
-	public static function startupAdminConfiguration(bool $return_config = false)
216
-	{
217
-		return self::selfClass()->setupSFSConfiguration($return_config);
218
-	}
219
-
220
-	/**
221
-	 * The actual settings page.
222
-	 *
223
-	 * @param bool $return_config If true, returns the configuration options for searches.
224
-	 *
225
-	 * @internal
226
-	 * @CalledIn SMF 2.0, SMF 2.1
227
-	 * @version 1.4.0
228
-	 * @since 1.0
229
-	 * @uses integrate_modify_modifications - Hook SMF2.0
230
-	 * @uses integrate_modify_modifications - Hook SMF2.1
231
-	 * @return void No return is generated
232
-	 */
233
-	private function setupSFSConfiguration(bool $return_config = false): array
234
-	{
235
-		global $scripturl, $context, $settings, $sc, $modSettings;
236
-
237
-		$config_vars = array(
238
-				array('title', 'sfsgentitle', 'label' => $this->SFSclass->txt('sfs_general_title')),
239
-
240
-				array('check', 'sfs_enabled'),
241
-				array('int', 'sfs_expire'),
242
-			'',
243
-				array('select', 'sfs_required', array(
244
-					'any' => $this->SFSclass->txt('sfs_required_any'),
245
-					'email|ip' => $this->SFSclass->txt('sfs_required_email_ip'),
246
-					'email|username' => $this->SFSclass->txt('sfs_required_email_username'),
247
-					'username|ip' => $this->SFSclass->txt('sfs_required_username_ip'),
248
-				)),
249
-			'',
250
-				array('check', 'sfs_emailcheck'),
251
-				array('check', 'sfs_usernamecheck'),
252
-				array('float', 'sfs_username_confidence', 'step' => '0.01'),
253
-				array('check', 'sfs_ipcheck'),
254
-				array('check', 'sfs_ipcheck_autoban'),
255
-			'',
256
-				array('select', 'sfs_region', $this->SFSclass->sfsServerMapping('config')),
257
-			'',
258
-				array('check', 'sfs_wildcard_email'),
259
-				array('check', 'sfs_wildcard_username'),
260
-				array('check', 'sfs_wildcard_ip'),
261
-			'',
262
-				array('select', 'sfs_tor_check', array(
263
-					0 => $this->SFSclass->txt('sfs_tor_check_block'),
264
-					1 => $this->SFSclass->txt('sfs_tor_check_ignore'),
265
-					2 => $this->SFSclass->txt('sfs_tor_check_bad'),
266
-				)),
267
-			'',
268
-				array('check', 'sfs_enablesubmission'),
269
-				array('text', 'sfs_apikey'),
270
-			'',
271
-				array('title', 'sfsverftitle', 'label' => $this->SFSclass->txt('sfs_verification_title')),
272
-				array('desc', 'sfsverfdesc', 'label' => $this->SFSclass->txt('sfs_verification_desc')),
273
-				array('select', 'sfs_verification_options', array(
274
-					'post' => $this->SFSclass->txt('sfs_verification_options_post'),
275
-					'report' => $this->SFSclass->txt('sfs_verification_options_report'),
276
-					'search' => $this->SFSclass->txt('sfs_verification_options_search'),
277
-				), 'multiple' => true),			
278
-				array('text', 'sfs_verification_options_extra', 'subtext' => $this->SFSclass->txt('sfs_verification_options_extra_subtext')),
279
-
280
-			'',
281
-				array('select', 'sfs_verOptionsMembers', array(
282
-					'post' => $this->SFSclass->txt('sfs_verification_options_post'),
283
-					'search' => $this->SFSclass->txt('sfs_verification_options_search'),
284
-				), 'multiple' => true),
285
-				array('text', 'sfs_verOptionsMemExtra', 'subtext' => $this->SFSclass->txt('sfs_verification_options_extra_subtext')),
286
-				array('int', 'sfs_verfOptMemPostThreshold'),
287
-			'',
288
-				array('check', 'sfs_log_debug'),
289
-		);
290
-
291
-		if ($return_config)
292
-			return $config_vars;
293
-
294
-		// Saving?
295
-		if (isset($_GET['save']))
296
-		{
297
-			// Turn the defaults off.
298
-			$this->SFSclass->unloadDefaults();
299
-			checkSession();
300
-
301
-			// If we are automatically banning IPs, make sure we have a ban group.
302
-			if (isset($_POST['sfs_ipcheck_autoban']) && empty($modSettings['sfs_ipcheck_autoban_group']))
303
-				$this->SFSclass->createBanGroup(true);
304
-
305
-			saveDBSettings($config_vars);
306
-
307
-			writeLog();
308
-			redirectexit($this->adminPageURL);
309
-		}
310
-
311
-		$context['post_url'] = $this->adminPageURL . ';save';
312
-
313
-		prepareDBSettingContext($config_vars);
314
-
315
-		return array();
316
-	}
317
-
318
-	/**
319
-	 * In some software/versions, we can hook into the logs section.
320
-	 * In others we hook into the modifications settings.
321
-	 *
322
-	 * @param array $log_functions All possible log functions.
323
-	 *
324
-	 * @api
325
-	 * @CalledIn SMF 2.1
326
-	 * @See SFSA::startupLogs
327
-	 * @version 1.0
328
-	 * @since 1.0
329
-	 * @uses integrate_manage_logs - Hook SMF2.1
330
-	 * @return void No return is generated
331
-	 */
332
-	public static function hook_manage_logs(array &$log_functions): bool
333
-	{
334
-		// Add our logs sub action.
335
-        $log_functions['sfslog'] = array('SFS-Subs-Logs.php', 'SFSL::startupLogs');
336
-
337
-		return self::selfClass()->AddToLogMenu($log_functions);
338
-	}
339
-
340
-	/**
341
-	 * Add the SFS logs to the log menu.
342
-	 *
343
-	 * @param array $log_functions All possible log functions.
344
-	 *
345
-	 * @CalledIn SMF 2.1
346
-	 * @See SFSA::startupLogs
347
-	 * @version 1.1
348
-	 * @since 1.1
349
-	 * @return void No return is generated
350
-	 */
351
-	public function AddToLogMenu(array &$log_functions): bool
352
-	{
353
-		global $context;
354
-
355
-		$context[$context['admin_menu_name']]['tab_data']['tabs']['sfslog'] = array(
356
-			'description' => $this->SFSclass->txt('sfs_admin_logs'),
357
-		);
358
-
359
-		return true;
360
-	}
361
-
362
-	/**
363
-	 * Log startup caller.
364
-	 * This has a $return_config just for simply complying with properly for searching the admin panel.
365
-	 *
366
-	 * @param bool $return_config If true, returns empty array to prevent breaking old SMF installs.
367
-	 *
368
-	 * @api
369
-	 * @CalledIn SMF 2.1
370
-	 * @See SFSA::loadLogs
371
-	 * @version 1.0
372
-	 * @since 1.0
373
-	 * @uses hook_manage_logs - Hook SMF2.1
374
-	 * @uses setupModifyModifications - Injected SMF2.0
375
-	 * @return void No return is generated
376
-	 */
377
-	public static function startupLogs(bool $return_config = false): array
378
-	{
379
-		return self::selfClass()->loadLogs();
380
-	}
381
-
382
-	/**
383
-	 * Actually show the logs.
384
-	 * This has a $return_config just for simply complying with properly for searching the admin panel.
385
-	 *
386
-	 * @param bool $return_config If true, returns empty array to prevent breaking old SMF installs.
387
-	 *
388
-	 * @api
389
-	 * @CalledIn SMF2.0, SMF 2.1
390
-	 * @See SFSA::getSFSLogEntries
391
-	 * @See SFSA::getSFSLogEntriesCount
392
-	 * @version 1.0
393
-	 * @since 1.0
394
-	 * @uses hook_manage_logs - Hook SMF2.1
395
-	 * @uses setupModifyModifications - Injected SMF2.0
396
-	 * @return void No return is generated
397
-	 */
398
-	public function loadLogs(bool $return_config = false): array
399
-	{
400
-		global $context, $smcFunc, $sourcedir;
401
-
402
-		// No Configs.
403
-		if ($return_config)
404
-			return array();
405
-
406
-		loadLanguage('Modlog');
407
-
408
-		$context['form_url'] = $this->adminLogURL;
409
-		$context['log_url'] = $this->adminLogURL;
410
-		$context['page_title'] = $this->SFSclass->txt('sfs_admin_logs');
411
-		$this->canDeleteLogs = allowedTo('admin_forum');
412
-
413
-		// Remove all..
414
-		if ((isset($_POST['removeall']) || isset($_POST['delete'])) && $this->canDeleteLogs)
415
-			$this->handleLogDeletes();
416
-
417
-		$sort_types = array(
418
-			'id_type' =>'l.id_type',
419
-			'log_time' => 'l.log_time',
420
-			'url' => 'l.url',
421
-			'member' => 'mem.id_member',
422
-			'username' => 'l.username',
423
-			'email' => 'l.email',
424
-			'ip' => 'l.ip',
425
-			'ip2' => 'l.ip2',
426
-		);
427
-
428
-		$context['order'] = isset($_REQUEST['sort']) && isset($sort_types[$_REQUEST['sort']]) ? $_REQUEST['sort'] : 'time';
429
-
430
-		// Handle searches.
431
-		$this->handleLogSearch($context['log_url']);
432
-
433
-		require_once($sourcedir . '/Subs-List.php');
434
-
435
-		$listOptions = array(
436
-			'id' => 'sfslog_list',
437
-			'title' => $this->SFSclass->txt('sfs_admin_logs'),
438
-			'width' => '100%',
439
-			'items_per_page' => '50',
440
-			'no_items_label' => $this->SFSclass->txt('sfs_log_no_entries_found'),
441
-			'base_href' => $context['log_url'],
442
-			'default_sort_col' => 'time',
443
-			'get_items' => $this->loadLogsGetItems(),
444
-			'get_count' => $this->loadLogsGetCount(),
445
-			// This assumes we are viewing by user.
446
-			'columns' => array(
447
-				'type' => $this->loadLogsColumnType(),
448
-				'time' => $this->loadLogsColumnTime(),
449
-				'url' => $this->loadLogsColumnURL(),
450
-				'member' => $this->loadLogsColumnMember(),
451
-				'username' => $this->loadLogsColumnUsername(),
452
-				'email' => $this->loadLogsColumnEmail(),
453
-				'ip' => $this->loadLogsColumnIP(),
454
-				'ip2' => $this->loadLogsColumnIP(true),
455
-				'checks' => $this->loadLogsColumnChecks(),
456
-				'result' => $this->loadLogsColumnResult(),
457
-				'delete' => $this->loadLogsColumnDelete(),
458
-			),
459
-			'form' => array(
460
-				'href' => $context['form_url'],
461
-				'include_sort' => true,
462
-				'include_start' => true,
463
-				'hidden_fields' => array(
464
-					$context['session_var'] => $context['session_id'],
465
-					'params' => $this->search_params
466
-				),
467
-			),
468
-			'additional_rows' => array(
469
-				$this->loadLogsGetAddtionalRow(),
470
-			),
471
-		);
472
-
473
-		// Create the watched user list.
474
-		createList($listOptions);
475
-
476
-		$context['sub_template'] = 'show_list';
477
-		$context['default_list'] = 'sfslog_list';
478
-
479
-		return array();
480
-	}
481
-
482
-	/**
483
-	 * Handle when we want to delete a log and what to do.
484
-	 *
485
-	 * @internal
486
-	 * @CalledIn SMF2.0, SMF 2.1
487
-	 * @version 1.1
488
-	 * @since 1.1
489
-	 * @return void Nothing is returned, the logs are deleted as requested and admin redirected.
490
-	 */
491
-	private function handleLogDeletes(): void
492
-	{
493
-		if (isset($_POST['removeall']) && $this->canDeleteLogs)
494
-			$this->removeAllLogs();
495
-		elseif (!empty($_POST['remove']) && isset($_POST['delete']) && $this->canDeleteLogs)
496
-			$this->removeLogs(array_unique($_POST['delete']));
497
-	}
498
-
499
-	/**
500
-	 * loadLogs - Get Items.
501
-	 *
502
-	 * @internal
503
-	 * @CalledIn SMF2.0, SMF 2.1
504
-	 * @version 1.1
505
-	 * @since 1.1
506
-	 * @return array The options for the get_items
507
-	 */
508
-	private function loadLogsGetItems(): array
509
-	{
510
-		return array(
511
-			'function' => array($this, 'getSFSLogEntries'),
512
-			'params' => array(
513
-				(!empty($this->logSearch['string']) ? ' INSTR({raw:sql_type}, {string:search_string})' : ''),
514
-				array('sql_type' => $this->search_params_column, 'search_string' => $this->logSearch['string']),
515
-			),
516
-		);
517
-	}
518
-
519
-	/**
520
-	 * loadLogs - Get Count.
521
-	 *
522
-	 * @internal
523
-	 * @CalledIn SMF2.0, SMF 2.1
524
-	 * @version 1.1
525
-	 * @since 1.1
526
-	 * @return array The options for the get_items
527
-	 */
528
-	private function loadLogsGetCount(): array
529
-	{
530
-		return array(
531
-			'function' => array($this, 'getSFSLogEntriesCount'),
532
-			'params' => array(
533
-				(!empty($this->logSearch['string']) ? ' INSTR({raw:sql_type}, {string:search_string})' : ''),
534
-				array('sql_type' => $this->search_params_column, 'search_string' => $this->logSearch['string']),
535
-			),
536
-		);
537
-	}
538
-
539
-	/**
540
-	 * loadLogs - Load an additional row, for mostly deleting stuff.
541
-	 *
542
-	 * @internal
543
-	 * @CalledIn SMF2.0, SMF 2.1
544
-	 * @version 1.1
545
-	 * @since 1.1
546
-	 * @return array The options for the get_items
547
-	 */
548
-	private function loadLogsGetAddtionalRow(): array
549
-	{
550
-		global $smcFunc;
551
-
552
-		return array(
553
-			'position' => 'below_table_data',
554
-			'value' => '
555
-				' . $this->SFSclass->txt('sfs_log_search') . ' (' . $this->logSearch['label'] . '):
556
-				<input type="text" name="search" size="18" value="' . $smcFunc['htmlspecialchars']($this->logSearch['string']) . '" class="input_text" /> <input type="submit" name="is_search" value="' . $this->SFSclass->txt('modlog_go') . '" class="button_submit" />
557
-				' . ($this->canDeleteLogs ? ' |
558
-					<input type="submit" name="remove" value="' . $this->SFSclass->txt('modlog_remove') . '" class="button_submit" />
559
-					<input type="submit" name="removeall" value="' . $this->SFSclass->txt('modlog_removeall') . '" class="button_submit" />' : ''),
560
-		);
561
-	}
562
-
563
-
564
-	/**
565
-	 * loadLogs - Column - Type.
566
-	 *
567
-	 * @internal
568
-	 * @CalledIn SMF2.0, SMF 2.1
569
-	 * @version 1.1
570
-	 * @since 1.1
571
-	 * @return array The options for the column
572
-	 */
573
-	private function loadLogsColumnType(): array
574
-	{
575
-		return array(
576
-			'header' => array(
577
-				'value' => $this->SFSclass->txt('sfs_log_header_type'),
578
-				'class' => 'lefttext',
579
-			),
580
-			'data' => array(
581
-				'db' => 'type',
582
-				'class' => 'smalltext',
583
-			),
584
-			'sort' => array(
585
-			),
586
-		);
587
-	}
588
-
589
-	/**
590
-	 * loadLogs - Column - Time.
591
-	 *
592
-	 * @internal
593
-	 * @CalledIn SMF2.0, SMF 2.1
594
-	 * @version 1.1
595
-	 * @since 1.1
596
-	 * @return array The options for the column
597
-	 */
598
-	private function loadLogsColumnTime(): array
599
-	{
600
-		return array(
601
-			'header' => array(
602
-				'value' => $this->SFSclass->txt('sfs_log_header_time'),
603
-				'class' => 'lefttext',
604
-			),
605
-			'data' => array(
606
-				'db' => 'time',
607
-				'class' => 'smalltext',
608
-			),
609
-			'sort' => array(
610
-				'default' => 'l.log_time DESC',
611
-				'reverse' => 'l.log_time',
612
-			),
613
-		);
614
-	}
615
-
616
-	/**
617
-	 * loadLogs - Column - URL.
618
-	 *
619
-	 * @internal
620
-	 * @CalledIn SMF2.0, SMF 2.1
621
-	 * @version 1.1
622
-	 * @since 1.1
623
-	 * @return array The options for the column
624
-	 */
625
-	private function loadLogsColumnURL(): array
626
-	{
627
-		return array(
628
-			'header' => array(
629
-				'value' => $this->SFSclass->txt('sfs_log_header_url'),
630
-				'class' => 'lefttext',
631
-			),
632
-			'data' => array(
633
-				'db' => 'url',
634
-				'class' => 'smalltext',
635
-				'style' => 'word-break: break-word;',
636
-			),
637
-			'sort' => array(
638
-				'default' => 'l.url DESC',
639
-				'reverse' => 'l.url',
640
-			),
641
-		);
642
-	}
643
-
644
-	/**
645
-	 * loadLogs - Column - Member.
646
-	 *
647
-	 * @internal
648
-	 * @CalledIn SMF2.0, SMF 2.1
649
-	 * @version 1.1
650
-	 * @since 1.1
651
-	 * @return array The options for the column
652
-	 */
653
-	private function loadLogsColumnMember(): array
654
-	{
655
-		return array(
656
-			'header' => array(
657
-				'value' => $this->SFSclass->txt('sfs_log_header_member'),
658
-				'class' => 'lefttext',
659
-			),
660
-			'data' => array(
661
-				'db' => 'member_link',
662
-				'class' => 'smalltext',
663
-			),
664
-			'sort' => array(
665
-				'default' => 'mem.id_member',
666
-				'reverse' => 'mem.id_member DESC',
667
-			),
668
-		);
669
-	}
670
-
671
-	/**
672
-	 * loadLogs - Column - Username.
673
-	 *
674
-	 * @internal
675
-	 * @CalledIn SMF2.0, SMF 2.1
676
-	 * @version 1.1
677
-	 * @since 1.1
678
-	 * @return array The options for the column
679
-	 */
680
-	private function loadLogsColumnUsername(): array
681
-	{
682
-		return array(
683
-			'header' => array(
684
-				'value' => $this->SFSclass->txt('sfs_log_header_username'),
685
-				'class' => 'lefttext',
686
-			),
687
-			'data' => array(
688
-				'db' => 'username',
689
-				'class' => 'smalltext',
690
-			),
691
-			'sort' => array(
692
-				'default' => 'l.username',
693
-				'reverse' => 'l.username DESC',
694
-			),
695
-		);
696
-	}
697
-
698
-	/**
699
-	 * loadLogs - Column - Email.
700
-	 *
701
-	 * @internal
702
-	 * @CalledIn SMF2.0, SMF 2.1
703
-	 * @version 1.1
704
-	 * @since 1.1
705
-	 * @return array The options for the column
706
-	 */
707
-	private function loadLogsColumnEmail(): array
708
-	{
709
-		return array(
710
-			'header' => array(
711
-				'value' => $this->SFSclass->txt('sfs_log_header_email'),
712
-				'class' => 'lefttext',
713
-			),
714
-			'data' => array(
715
-				'db' => 'email',
716
-				'class' => 'smalltext',
717
-			),
718
-			'sort' => array(
719
-				'default' => 'l.email',
720
-				'reverse' => 'l.email DESC',
721
-			),
722
-		);
723
-	}
724
-
725
-	/**
726
-	 * loadLogs - Column - IP.
727
-	 *
728
-	 * @param string $ip2 If true, use ip2
729
-	 * @internal
730
-	 * @CalledIn SMF2.0, SMF 2.1
731
-	 * @version 1.1
732
-	 * @since 1.1
733
-	 * @return array The options for the column
734
-	 */
735
-	private function loadLogsColumnIP(bool $ip2 = false): array
736
-	{
737
-		return array(
738
-			'header' => array(
739
-				'value' => $this->SFSclass->txt('sfs_log_header_ip' . ($ip2 ? '2' : '')),
740
-				'class' => 'lefttext',
741
-			),
742
-			'data' => array(
743
-				'db' => 'ip' . ($ip2 ? '2' : ''),
744
-				'class' => 'smalltext',
745
-			),
746
-			'sort' => array(
747
-				'default' => 'l.ip' . ($ip2 ? '2' : ''),
748
-				'reverse' => 'l.ip' . ($ip2 ? '2' : '') . ' DESC',
749
-			),
750
-		);
751
-	}
752
-
753
-	/**
754
-	 * loadLogs - Column - Checks.
755
-	 *
756
-	 * @internal
757
-	 * @CalledIn SMF2.0, SMF 2.1
758
-	 * @version 1.1
759
-	 * @since 1.1
760
-	 * @return array The options for the column
761
-	 */
762
-	private function loadLogsColumnChecks(): array
763
-	{
764
-		return array(
765
-			'header' => array(
766
-				'value' => $this->SFSclass->txt('sfs_log_checks'),
767
-				'class' => 'lefttext',
768
-			),
769
-			'data' => array(
770
-				'db' => 'checks',
771
-				'class' => 'smalltext',
772
-				'style' => 'word-break: break-word;',
773
-			),
774
-			'sort' => array(),
775
-		);
776
-	}
777
-
778
-	/**
779
-	 * loadLogs - Column - Result.
780
-	 *
781
-	 * @internal
782
-	 * @CalledIn SMF2.0, SMF 2.1
783
-	 * @version 1.1
784
-	 * @since 1.1
785
-	 * @return array The options for the column
786
-	 */
787
-	private function loadLogsColumnResult(): array
788
-	{
789
-		return array(
790
-			'header' => array(
791
-				'value' => $this->SFSclass->txt('sfs_log_result'),
792
-				'class' => 'lefttext',
793
-			),
794
-			'data' => array(
795
-				'db' => 'result',
796
-				'class' => 'smalltext',
797
-				'style' => 'word-break: break-word;',
798
-			),
799
-			'sort' => array(),
800
-		);
801
-	}
802
-
803
-	/**
804
-	 * loadLogs - Column - Delete.
805
-	 *
806
-	 * @internal
807
-	 * @CalledIn SMF2.0, SMF 2.1
808
-	 * @version 1.1
809
-	 * @since 1.1
810
-	 * @return array The options for the column
811
-	 */
812
-	private function loadLogsColumnDelete(): array
813
-	{
814
-		return array(
815
-			'header' => array(
816
-				'value' => '<input type="checkbox" name="all" class="input_check" onclick="invertAll(this, this.form);" />',
817
-			),
818
-			'data' => array(
819
-				'function' => function($entry)
820
-				{
821
-					return '<input type="checkbox" class="input_check" name="delete[]" value="' . $entry['id'] . '"' . ($entry['editable'] ? '' : ' disabled="disabled"') . ' />';
822
-				},
823
-				'style' => 'text-align: center;',
824
-			),
825
-		);
826
-	}
827
-
828
-	/**
829
-	 * Get the log data and returns it ready to go for GenericList handling.
830
-	 *
831
-	 * @param int $start The index for where we offset or start at for the list
832
-	 * @param int $items_per_page How many items we are going to show on this page.
833
-	 * @param string $sort The column we are sorting by.
834
-	 * @param string $query_string The search string we are using to filter log data.
835
-	 * @param array $query_params Extra parameters for searching.
836
-	 *
837
-	 * @api
838
-	 * @CalledIn SMF 2.0, SMF 2.1
839
-	 * @See SFSA::loadLogs
840
-	 * @version 1.0
841
-	 * @since 1.0
842
-	 * @uses hook_manage_logs - Hook SMF2.1
843
-	 * @uses setupModifyModifications - Injected SMF2.0
844
-	 * @return void No return is generated
845
-	 */
846
-	public function getSFSLogEntries(int $start, int $items_per_page, string $sort, string $query_string = '', array $query_params = array()): array
847
-	{
848
-		global $scripturl, $context, $smcFunc;
849
-
850
-		// Fetch all of our logs.
851
-		$result = $smcFunc['db_query']('', '
852
-			SELECT
853
-				l.id_sfs,
854
-				l.id_type,
855
-				l.log_time,
856
-				l.url,
857
-				l.id_member,
858
-				l.username,
859
-				l.email,
860
-				l.ip,
861
-				l.ip2,
862
-				l.checks,
863
-				l.result,
864
-				mem.real_name,
865
-				mg.group_name
866
-			FROM {db_prefix}log_sfs AS l
867
-				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = l.id_member)
868
-				LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = CASE WHEN mem.id_group = {int:reg_group_id} THEN mem.id_post_group ELSE mem.id_group END)
869
-				WHERE id_type IS NOT NULL'
870
-				. (!empty($query_string) ? '
871
-					AND ' . $query_string : '') . '
872
-			ORDER BY ' . $sort . '
873
-			LIMIT ' . $start . ', ' . $items_per_page,
874
-			array_merge($query_params, array(
875
-				'reg_group_id' => 0,
876
-			))
877
-		);
878
-
879
-		$entries = array();
880
-		while ($row = $smcFunc['db_fetch_assoc']($result))
881
-		{
882
-			$entries[$row['id_sfs']] = array(
883
-				'id' => $row['id_sfs'],
884
-				'type' => $this->SFSclass->txt('sfs_log_types_' . $row['id_type']),
885
-				'time' => timeformat($row['log_time']),
886
-				'url' => preg_replace('~http(s)?://~i', 'hxxp\\1://', $row['url']),
887
-				'timestamp' => $row['log_time'],
888
-				'member_link' => $row['id_member'] ? '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>' : (empty($row['real_name']) ? ($this->SFSclass->txt('guest') . (!empty($row['extra']['member_acted']) ? ' (' . $row['extra']['member_acted'] . ')' : '')) : $row['real_name']),
889
-				'username' => $row['username'],
890
-				'email' => $row['email'],
891
-				'ip' => '<a href="' . sprintf($this->urlSFSipCheck, $row['ip']) . '">' . $row['ip'] . '</a>',
892
-				'ip2' => '<a href="' . sprintf($this->urlSFSipCheck, $row['ip2']) . '">' . $row['ip2'] . '</a>',
893
-				'editable' => true, //time() > $row['log_time'] + $this->hoursDisabled * 3600,
894
-				'checks_raw' => $row['checks'],
895
-				'result_raw' => $row['result'],
896
-			);
897
-
898
-			$checksDecoded = $this->SFSclass->decodeJSON($row['checks']);
899
-
900
-			// If we know what check triggered this, link it up to be searched.
901
-			if ($row['id_type'] == 1)
902
-				$entries[$row['id_sfs']]['checks'] = '<a href="' . sprintf($this->urlSFSsearch, $checksDecoded['value']) . '">' . $checksDecoded['value'] . '</a>';
903
-			elseif ($row['id_type'] == 2)
904
-				$entries[$row['id_sfs']]['checks'] = '<a href="' . sprintf($this->urlSFSsearch, $checksDecoded['value']) . '">' . $checksDecoded['value'] . '</a>';
905
-			elseif ($row['id_type'] == 3)
906
-				$entries[$row['id_sfs']]['checks'] = '<a href="' . sprintf($this->urlSFSsearch, $checksDecoded['value']) . '">' . $checksDecoded['value'] . '</a>';
907
-			// No idea what triggered it, parse it out cleanly.  Could be debug data as well.
908
-			else
909
-			{
910
-				$entries[$row['id_sfs']]['checks'] = '';
911
-
912
-				foreach ($checksDecoded as $ckey => $vkey)
913
-					foreach ($vkey as $key => $value)
914
-						$entries[$row['id_sfs']]['checks'] .= ucfirst($key) . ':' . $value . '<br>';					
915
-			}
916
-
917
-			// This tells us what it matched on exactly.
918
-			if (strpos($row['result'], ',') !== false)
919
-			{
920
-				list($resultType, $resultMatch, $extra) = explode(',', $row['result'] . ',,,');
921
-				$entries[$row['id_sfs']]['result'] = sprintf($this->SFSclass->txt('sfs_log_matched_on'), $resultType, $resultMatch);
922
-
923
-				// If this was a IP ban, note it.
924
-				if ($resultType == 'ip' && !empty($extra))
925
-					$entries[$row['id_sfs']]['result'] .= ' ' . $this->SFSclass->txt('sfs_log_auto_banned');			
926
-				if ($resultType == 'username' && !empty($extra))
927
-					$entries[$row['id_sfs']]['result'] .= ' ' . sprintf($this->SFSclass->txt('sfs_log_confidence'), $extra);			
928
-			}
929
-			else
930
-				$entries[$row['id_sfs']]['result'] = $row['result'];
931
-			
932
-		}
933
-		$smcFunc['db_free_result']($result);
934
-
935
-		return $entries;
936
-	}
937
-
938
-	/**
939
-	 * Get the log counts and returns it ready to go for GenericList handling.
940
-	 *
941
-	 * @param string $query_string The search string we are using to filter log data.
942
-	 * @param array $query_params Extra parameters for searching.
943
-	 *
944
-	 * @api
945
-	 * @CalledIn SMF 2.0, SMF 2.1
946
-	 * @See SFSA::loadLogs
947
-	 * @version 1.0
948
-	 * @since 1.0
949
-	 * @uses hook_manage_logs - Hook SMF2.1
950
-	 * @uses setupModifyModifications - Injected SMF2.0
951
-	 * @return void No return is generated
952
-	 */
953
-	public function getSFSLogEntriesCount(string $query_string = '', array $query_params = array()): int
954
-	{
955
-		global $smcFunc, $user_info;
956
-
957
-		$result = $smcFunc['db_query']('', '
958
-			SELECT COUNT(*)
959
-			FROM {db_prefix}log_sfs AS l
960
-				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = l.id_member)
961
-				LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = CASE WHEN mem.id_group = {int:reg_group_id} THEN mem.id_post_group ELSE mem.id_group END)
962
-				WHERE id_type IS NOT NULL'
963
-				. (!empty($query_string) ? '
964
-					AND ' . $query_string : ''),
965
-			array_merge($query_params, array(
966
-				'reg_group_id' => 0,
967
-			))
968
-		);
969
-		list ($entry_count) = $smcFunc['db_fetch_row']($result);
970
-		$smcFunc['db_free_result']($result);
971
-
972
-		return (int) $entry_count;
973
-	}
974
-
975
-	/**
976
-	 * Get params
977
-	 *
978
-	 * @internal
979
-	 * @CalledIn SMF 2.0, SMF 2.1
980
-	 * @version 1.1
981
-	 * @since 1.0
982
-	 * @return string The column we are searching.
983
-	 */
984
-	public function get(string $var)
985
-	{
986
-		if (isset($this->{$var}))
987
-			return $this->{$var};
988
-	}
989
-
990
-	/**
991
-	 * Remove all logs, except those less than 24 hours old.
992
-	 *
993
-	 * @api
994
-	 * @CalledIn SMF 2.0, SMF 2.1
995
-	 * @See SFSA::loadLogs
996
-	 * @version 1.0
997
-	 * @since 1.0
998
-	 * @return void No return is generated
999
-	 */
1000
-	private function removeAllLogs(): void
1001
-	{
1002
-		global $smcFunc;
1003
-
1004
-		checkSession();
1005
-
1006
-		$smcFunc['db_query']('', '
1007
-			DELETE FROM {db_prefix}log_sfs
1008
-			WHERE log_time < {int:twenty_four_hours_wait}',
1009
-			array(
1010
-				'twenty_four_hours_wait' => time() - $this->hoursDisabled * 3600,
1011
-			)
1012
-		);
1013
-	}
1014
-
1015
-	/**
1016
-	 * Remove specific logs, except those less than 24 hours old.
1017
-	 *
1018
-	 * @param array $entries A array of the ids that we want to remove.
1019
-	 *
1020
-	 * @api
1021
-	 * @CalledIn SMF 2.0, SMF 2.1
1022
-	 * @See SFSA::loadLogs
1023
-	 * @version 1.0
1024
-	 * @since 1.0
1025
-	 * @return void No return is generated
1026
-	 */
1027
-	private function removeLogs(array $entries): void
1028
-	{
1029
-		global $smcFunc;
1030
-
1031
-		checkSession();
1032
-
1033
-		$smcFunc['db_query']('', '
1034
-			DELETE FROM {db_prefix}log_sfs
1035
-			WHERE id_sfs IN ({array_string:delete_actions})
1036
-				AND log_time < {int:twenty_four_hours_wait}',
1037
-			array(
1038
-				'twenty_four_hours_wait' => time() - $this->hoursDisabled * 3600,
1039
-				'delete_actions' => $entries,
1040
-			)
1041
-		);
1042
-	}
1043
-
1044
-	/**
1045
-	 * Handle searching for logs.
1046
-	 *
1047
-	 * @param string $url The base_href
1048
-	 * @internal
1049
-	 * @CalledIn SMF 2.0, SMF 2.1
1050
-	 * @version 1.0
1051
-	 * @since 1.0
1052
-	 * @return void No return is generated here.
1053
-	 */
1054
-	private function handleLogSearch(string &$url): void
1055
-	{
1056
-		global $context, $txt;
1057
-
1058
-		// If we have some data from a search, lets bring it back out.
1059
-		$this->search_params = $this->handleLogSearchParams();
1060
-
1061
-		// What we can search.
1062
-		$this->search_types = $this->handleLogSearchTypes();
1063
-		$this->search_params_string = $this->handleLogSearchParamsString();
1064
-		$this->search_params_type = $this->handleLogSearchParamsType();
1065
-
1066
-		$this->search_params_column = $this->search_types[$this->search_params_type]['sql'];
1067
-
1068
-		// Setup the search context.
1069
-		$this->search_params = empty($this->search_params_string) ? '' : base64_encode(json_encode(array(
1070
-			'string' => $this->search_params_string,
1071
-			'type' => $this->search_params_type,
1072
-		)));
1073
-		$this->logSearch = array(
1074
-			'string' => $this->search_params_string,
1075
-			'type' => $this->search_params_type,
1076
-			'label' => $this->search_types[$this->search_params_type]['label'],
1077
-		);
1078
-
1079
-		if (!empty($this->search_params))
1080
-			$url .= ';params=' . $this->search_params;
1081
-	}
1082
-
1083
-	/**
1084
-	 * Handle Search Params
1085
-	 *
1086
-	 * @internal
1087
-	 * @CalledIn SMF 2.0, SMF 2.1
1088
-	 * @version 1.1
1089
-	 * @since 1.0
1090
-	 * @return bool True upon success, false otherwise.
1091
-	 */
1092
-	private function handleLogSearchParams(): array
1093
-	{
1094
-		// If we have something to search for saved, get it back out.
1095
-		if (!empty($_REQUEST['params']) && empty($_REQUEST['is_search']))
1096
-		{
1097
-			$search_params = base64_decode(strtr($_REQUEST['params'], array(' ' => '+')));
1098
-			$search_params = $this->SFSclass->decodeJSON($search_params);
1099
-
1100
-			if (!empty($search_params))
1101
-				return $search_params;
1102
-		}
1103
-	
1104
-		return array();
1105
-	}
1106
-
1107
-	/**
1108
-	 * Handle Search Types
1109
-	 *
1110
-	 * @internal
1111
-	 * @CalledIn SMF 2.0, SMF 2.1
1112
-	 * @version 1.1
1113
-	 * @since 1.0
1114
-	 * @return array The valid Search Types.
1115
-	 */
1116
-	private function handleLogSearchTypes(): array
1117
-	{
1118
-		return array(
1119
-			'url' => array('sql' => 'l.url', 'label' => $this->SFSclass->txt('sfs_log_search_url')),
1120
-			'member' => array('sql' => 'mem.real_name', 'label' => $this->SFSclass->txt('sfs_log_search_member')),
1121
-			'username' => array('sql' => 'l.username', 'label' => $this->SFSclass->txt('sfs_log_search_username')),
1122
-			'email' => array('sql' => 'l.email', 'label' => $this->SFSclass->txt('sfs_log_search_email')),
1123
-			'ip' => array('sql' => 'lm.ip', 'label' => $this->SFSclass->txt('sfs_log_search_ip')),
1124
-			'ip2' => array('sql' => 'lm.ip2', 'label' => $this->SFSclass->txt('sfs_log_search_ip2'))
1125
-		);
1126
-	}
1127
- 
1128
-	/**
1129
-	 * Handle Search Params String
1130
-	 *
1131
-	 * @internal
1132
-	 * @CalledIn SMF 2.0, SMF 2.1
1133
-	 * @version 1.1
1134
-	 * @since 1.0
1135
-	 * @return string What we are searching for, validated and cleaned.
1136
-	 */
1137
-	private function handleLogSearchParamsString(): string
1138
-	{
1139
-		if (!isset($this->search_params['string']) || (!empty($_REQUEST['search']) && $this->search_params['string'] != $_REQUEST['search']))
1140
-			return empty($_REQUEST['search']) ? '' : $_REQUEST['search'];
1141
-		else
1142
-			return $this->search_params['string'];
1143
-	}
1144
-
1145
-	/**
1146
-	 * Handle Search Params Type
1147
-	 *
1148
-	 * @internal
1149
-	 * @CalledIn SMF 2.0, SMF 2.1
1150
-	 * @version 1.1
1151
-	 * @since 1.0
1152
-	 * @return string The column we are searching.
1153
-	 */
1154
-	private function handleLogSearchParamsType(): string
1155
-	{
1156
-		global $context;
1157
-
1158
-		if (isset($_REQUEST['search_type']) || empty($this->search_params['type']) || !isset($this->search_types[$this->search_params['type']]))
1159
-			return isset($_REQUEST['search_type']) && isset($this->search_types[$_REQUEST['search_type']]) ? $_REQUEST['search_type'] : (isset($this->search_types[$context['order']]) ? $context['order'] : 'member');
1160
-		else
1161
-			return $this->search_params['type'];
1162
-	}
1163
-
1164
-	/**
1165
-	 * In some software/versions, we can hook into the members registration center section.
1166
-	 * In others we hook into the modifications settings.
1167
-	 *
1168
-	 * @param array $subActions All possible sub actions.
1169
-	 *
1170
-	 * @api
1171
-	 * @CalledIn SMF 2.1
1172
-	 * @See SFSA::startupTest
1173
-	 * @version 1.4.0
1174
-	 * @since 1.4.0
1175
-	 * @uses integrate_manage_registrations - Hook SMF2.1
1176
-	 * @return void No return is generated
1177
-	 */
1178
-	public static function hook_manage_registrations(array &$subActions): bool
1179
-	{
1180
-		global $context;
1181
-
1182
-		// Add our logs sub action.
1183
-		$subActions['sfstest'] = array('SFSA::startupTest', 'admin_forum');
1184
-
1185
-		if (isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'sfstest' && allowedTo('admin_forum'))
1186
-			$context['sub_action'] = 'sfstest';
1187
-
1188
-		return self::selfClass()->AddToRegCenterMenu($subActions);
1189
-	}
1190
-
1191
-	/**
1192
-	 * Add the SFS Test to the regcenter menu.
1193
-	 *
1194
-	 * @param array $log_functions All possible log functions.
1195
-	 *
1196
-	 * @CalledIn SMF 2.1
1197
-	 * @See SFSA::startupTest
1198
-	 * @version 1.4.0
1199
-	 * @since 1.4.0
1200
-	 * @return void No return is generated
1201
-	 */
1202
-	public function AddToRegCenterMenu(array &$subActions): bool
1203
-	{
1204
-		global $context;
1205
-
1206
-		$context[$context['admin_menu_name']]['tab_data']['tabs']['sfstest'] = array(
1207
-			'description' => $this->SFSclass->txt('sfs_admin_test_desc'),
1208
-		);
1209
-
1210
-		return true;
1211
-	}
1212
-
1213
-	/**
1214
-	 * Test API startup caller.
1215
-	 * This has a $return_config just for simply complying with properly for searching the admin panel.
1216
-	 *
1217
-	 * @param bool $return_config If true, returns empty array to prevent breaking old SMF installs.
1218
-	 *
1219
-	 * @api
1220
-	 * @CalledIn SMF 2.1
1221
-	 * @See SFSA::loadTestAPI
1222
-	 * @version 1.4.0
1223
-	 * @since 1.4.0
1224
-	 * @uses hook_manage_registrations - Hook SMF2.1
1225
-	 * @uses setupModifyModifications - Injected SMF2.0
1226
-	 * @return void No return is generated
1227
-	 */
1228
-	public static function startupTest(bool $return_config = false): array
1229
-	{
1230
-		return self::selfClass()->loadTestAPI();
1231
-	}
1232
-
1233
-	/**
1234
-	 * Actually do the test API.
1235
-	 * This has a $return_config just for simply complying with properly for searching the admin panel.
1236
-	 *
1237
-	 * @param bool $return_config If true, returns empty array to prevent breaking old SMF installs.
1238
-	 *
1239
-	 * @api
1240
-	 * @CalledIn SMF2.0, SMF 2.1
1241
-	 * @version 1.4.0
1242
-	 * @since 1.4.0
1243
-	 * @uses hook_manage_registrations - Hook SMF2.1
1244
-	 * @uses setupModifyModifications - Injected SMF2.0
1245
-	 * @return void No return is generated
1246
-	 */
1247
-	public function loadTestAPI(bool $return_config = false): array
1248
-	{
1249
-		global $context, $smcFunc, $user_info;
1250
-
1251
-		// No Configs.
1252
-		if ($return_config)
1253
-			return array();
1254
-
1255
-		$context['token_check'] = 'sfs_testapi';
1256
-		$this->SFSclass->loadLanguage();
1257
-
1258
-		// The reuslts output.
1259
-		$context['test_sent'] = isset($_POST['send']);
1260
-		$context['sfs_checks'] = array(
1261
-			'username' => array(
1262
-				0 => array(
1263
-					'enabled' => !empty($modSettings['sfs_usernamecheck']),
1264
-					'value' => !empty($_POST['username']) ? $smcFunc['htmlspecialchars']($_POST['username']) : $user_info['name'],
1265
-					'results' => null
1266
-				),
1267
-			),
1268
-			'email' => array(
1269
-				0 => array(
1270
-					'enabled' => !empty($modSettings['sfs_emailcheck']),
1271
-					'value' => !empty($_POST['email']) ? $smcFunc['htmlspecialchars']($_POST['email']) : $user_info['email'],
1272
-					'results' => null
1273
-				),
1274
-			),
1275
-			'ip' => array(
1276
-				0 => array(
1277
-					'enabled' => !empty($modSettings['sfs_ipcheck']),
1278
-					'value' => !empty($_POST['ip']) ? $smcFunc['htmlspecialchars']($_POST['ip']) : $user_info['ip'],
1279
-					'results' => null
1280
-				),
1281
-			),
1282
-		);
1283
-
1284
-		// Sending the data?
1285
-		if ($context['test_sent'])
1286
-		{
1287
-			checkSession();
1288
-			if (!$this->SFSclass->versionCheck('2.0', 'smf'))
1289
-				validateToken($context['token_check'], 'post');
1290
-
1291
-			$username = $smcFunc['htmlspecialchars']($_POST['username']);
1292
-			$email = $smcFunc['htmlspecialchars']($_POST['email']);
1293
-			$ip = $smcFunc['htmlspecialchars']($_POST['ip']);
1294
-				
1295
-			$response = $this->SFSclass->TestSFS(array(
1296
-					array('username' => $username),
1297
-					array('email' => $email),
1298
-					array('ip' => $ip),
1299
-			));
1300
-
1301
-			// No checks found? Can't do this.
1302
-			if (empty($response) || !is_array($response) || empty($response['success']))
1303
-				$context['test_api_error'] = $this->SFSclass->txt('sfs_request_failure_nodata');
1304
-			else
1305
-				// Parse all the responses out.
1306
-				foreach($context['sfs_checks'] as $key => &$res)
1307
-					$res[0] += $response[$key][0];
1308
-		}
1309
-
1310
-		// Load our template.
1311
-		loadTemplate('StopForumSpam');
1312
-		$context['sub_template'] = 'sfsa_testapi';
1313
-
1314
-		$context['sfs_test_url'] = $this->adminTestURL;
1315
-		if (!$this->SFSclass->versionCheck('2.0', 'smf'))
1316
-			createToken($context['token_check'], 'post');
1317
-		else
1318
-			unset($context['token_check']);
1319
-
1320
-		return array();
1321
-	}
1322
-}
1323 0
\ No newline at end of file
... ...
@@ -1,1581 +0,0 @@
1
-<?php
2
-
3
-/**
4
- * The Main class for Stop Forum Spam
5
- * @package StopForumSpam
6
- * @author SleePy <sleepy @ simplemachines (dot) org>
7
- * @copyright 2019
8
- * @license 3-Clause BSD https://opensource.org/licenses/BSD-3-Clause
9
- * @version 1.2
10
- */
11
-class SFS
12
-{
13
-	/**
14
-	 * @var array Our settings information used on saving/changing settings.
15
-	 */
16
-	private $changedSettings = array();
17
-	private $extraVerificationOptions = array();
18
-
19
-	/**
20
-	 * @var string Name of the software and its version.  This is so we can branch out from the same base.
21
-	 */
22
-	private $softwareName = 'smf';
23
-	private $softwareVersion = '2.1';
24
-
25
-	/**
26
-	 * @var array The block Types.
27
-	 */
28
-	private $blockTypeMap = array(
29
-		'username' => 1,
30
-		'email' => 2,
31
-		'ip' => 3
32
-	);
33
-
34
-	/**
35
-	 * Simple setup for the class to be used later correctly.
36
-	 * This simply loads the class into $smcFunc and we can grab this anywhere else later.
37
-	 *
38
-	 * @api
39
-	 * @CalledIn SMF 2.0, SMF 2.1
40
-	 * @version 1.0
41
-	 * @since 1.0
42
-	 * @uses integrate_pre_load - Hook SMF2.0
43
-	 * @uses integrate_pre_load - Hook SMF2.1
44
-	 * @return void No return is generated
45
-	 */
46
-	public static function hook_pre_load(): void
47
-	{
48
-		global $smcFunc, $sourcedir;
49
-
50
-		$smcFunc['classSFS'] = new SFS();
51
-	}
52
-
53
-	/**
54
-	 * Build the class, figure out what software/version we have.
55
-	 * Loads up the defaults.
56
-	 *
57
-	 * @CalledIn SMF 2.0, SMF 2.1
58
-	 * @version 1.0
59
-	 * @since 1.0
60
-	 * @return void No return is generated
61
-	 */
62
-	public function __construct()
63
-	{
64
-		global $smcFunc;
65
-
66
-		// Is this SMF 2.0?
67
-		if (!function_exists('loadCacheAccelerator'))
68
-			$this->softwareVersion = '2.0';
69
-
70
-		// Setup the defaults.
71
-		$this->loadDefaults();
72
-	}
73
-
74
-	/**
75
-	 * Handle registration events.
76
-	 *
77
-	 * @param array $regOptions An array from the software with all the registration optins we are going to use to register.
78
-	 * @param array $theme_vars An array from the software with all the possible theme settings we are going to use to register.
79
-	 *
80
-	 * @api
81
-	 * @CalledIn SMF 2.0, SMF 2.1
82
-	 * @CalledAt: action=signup, action=admin;area=regcenter;sa=register
83
-	 * @See SFS::checkRegisterRequest
84
-	 * @version 1.0
85
-	 * @since 1.0
86
-	 * @uses integrate_register - Hook SMF2.1
87
-	 * @uses integrate_register - Hook SMF2.0
88
-	 * @return bool True is success, no other bool is expeicifcly defined yet.
89
-	 */
90
-	public static function hook_register(array &$regOptions, array &$theme_vars): bool
91
-	{
92
-		global $smcFunc;
93
-		return $smcFunc['classSFS']->checkRegisterRequest($regOptions, $theme_vars);
94
-	}
95
-
96
-	/**
97
-	 * Something is attempting to register, we should check them out.
98
-	 *
99
-	 * @param array $regOptions An array from the software with all the registration optins we are going to use to register.
100
-	 * @param array $theme_vars An array from the software with all the possible theme settings we are going to use to register.
101
-	 *
102
-	 * @api
103
-	 * @CalledIn SMF 2.0, SMF 2.1
104
-	 * @CalledAt: action=signup, action=admin;area=regcenter;sa=register
105
-	 * @See SFS::checkRegisterRequest
106
-	 * @version 1.0
107
-	 * @since 1.0
108
-	 * @uses integrate_register - Hook SMF2.1
109
-	 * @uses integrate_register - Hook SMF2.0
110
-	 * @return bool True is success, no other bool is expeicifcly defined yet.
111
-	 */
112
-	private function checkRegisterRequest(array &$regOptions, array &$theme_vars): bool
113
-	{
114
-		// Admins are not spammers.. usually.
115
-		if ($regOptions['interface'] == 'admin')
116
-			return true;
117
-
118
-		// Pass everything and let us handle what options we pass on.  We pass the register_vars as these are what we have cleaned up.
119
-		return $this->sfsCheck(array(
120
-			array('username' => $regOptions['register_vars']['member_name']),
121
-			array('email' => $regOptions['register_vars']['email_address']),
122
-			array('ip' => $regOptions['register_vars']['member_ip']),
123
-			array('ip' => $regOptions['register_vars']['member_ip2']),
124
-		), 'register');
125
-	}
126
-
127
-	/**
128
-	 * The caller for a verification test.
129
-	 *
130
-	 * @param array $thisVerification An array from the software with all the verification information we have.
131
-	 * @param array $verification_errors An errors which exist from verification.
132
-	 *
133
-	 * @api
134
-	 * @CalledIn SMF 2.0, SMF 2.1
135
-	 * @See SFS::checkVerificationTest
136
-	 * @version 1.0
137
-	 * @since 1.0
138
-	 * @uses integrate_create_control_verification_test - Hook SMF2.1
139
-	 * @return bool True is success, no other bool is expeicifcly defined yet.
140
-	 */
141
-	public static function hook_create_control_verification_test(array $thisVerification, array &$verification_errors): bool
142
-	{
143
-		global $smcFunc;
144
-		return $smcFunc['classSFS']->checkVerificationTest($thisVerification, $verification_errors);
145
-	}
146
-
147
-	/**
148
-	 * The caller for a verification test.
149
-	 * SMF 2.0 calls this directly as we have no good hook.
150
-	 *
151
-	 * @param array $thisVerification An array from the software with all the verification information we have.
152
-	 * @param array $verification_errors An errors which exist from verification.
153
-	 *
154
-	 * @api
155
-	 * @CalledIn SMF 2.0, SMF 2.1
156
-	 * @version 1.2
157
-	 * @since 1.0
158
-	 * @uses create_control_verification - Hook SMF2.0
159
-	 * @uses integrate_create_control_verification_test - Hook SMF2.1
160
-	 * @return bool True is success, no other bool is expeicifcly defined yet.
161
-	 */
162
-	public function checkVerificationTest(array $thisVerification, array &$verification_errors): bool
163
-	{
164
-		global $user_info, $modSettings;
165
-
166
-		// Registration is skipped as we process that differently.
167
-		if ($thisVerification['id'] == 'register')
168
-			return true;
169
-
170
-		// Get our options data.
171
-		$options = $this->getVerificationOptions();
172
-
173
-		// Key => Extended checks.
174
-		$verificationMap = array(
175
-			'post' => true,
176
-			'report' => true,
177
-			'search' => $user_info['is_guest'] || empty($user_info['posts']) || $user_info['posts'] < $modSettings['sfs_verfOptMemPostThreshold'],
178
-		);
179
-
180
-		foreach ($verificationMap as $key => $extendedChecks)
181
-			if ($thisVerification['id'] == $key && in_array($key, $options))
182
-				return call_user_func(array($this, 'checkVerificationTest' . ucfirst($key)));
183
-
184
-		// Others areas.  We have to play a guessing game here.
185
-		return $this->checkVerificationTestExtra($thisVerification);
186
-	}
187
-
188
-	/**
189
-	 * The hook to setup profile menu.
190
-	 *
191
-	 * @param array $profile_areas All the profile areas.
192
-	 *
193
-	 * @api
194
-	 * @CalledIn SMF 2.1
195
-	 * @See SFS::setupProfileMenu
196
-	 * @version 1.1
197
-	 * @since 1.1
198
-	 * @uses integrate_pre_profile_areas - Hook SMF2.1
199
-	 * @return void the passed $profile_areas is modified.
200
-	 */
201
-	public static function hook_pre_profile_areas(array &$profile_areas): void
202
-	{
203
-		global $smcFunc;
204
-		$smcFunc['classSFS']->setupProfileMenu($profile_areas);
205
-	}
206
-
207
-	/**
208
-	 * The hook to setup profile menu.
209
-	 *
210
-	 * @param array $profile_areas All the profile areas.
211
-	 *
212
-	 * @api
213
-	 * @CalledIn SMF 2.1
214
-	 * @version 1.1
215
-	 * @since 1.1
216
-	 * @uses integrate_pre_profile_areas - Hook SMF2.1
217
-	 * @return void the passed $profile_areas is modified.
218
-	 */
219
-	public function setupProfileMenu(array &$profile_areas): void
220
-	{
221
-		$profile_areas['info']['areas']['sfs'] = [
222
-			'label' => $this->txt('sfs_profile'),
223
-			'file' => 'SFS.php',
224
-			'icon' => 'sfs.webp',
225
-			'function' => 'SFS::ProfileTrackSFS',
226
-			'permission' => [
227
-				'own' => ['moderate_forum'],
228
-				'any' => ['moderate_forum'],
229
-			],
230
-		];
231
-
232
-		// SMF 2.0 can't call objects or classes.
233
-		if ($this->versionCheck('2.0', 'smf'))
234
-		{
235
-			function ProfileTrackSFS20(int $memID)
236
-			{
237
-				return SFS::ProfileTrackSFS($memID);
238
-			}
239
-			$profile_areas['info']['areas']['sfs']['function'] = 'ProfileTrackSFS20';
240
-		}
241
-	}
242
-
243
-	/**
244
-	 * The caller for a profile check.
245
-	 *
246
-	 * @param int $memID The id of the member we are checking.
247
-	 *
248
-	 * @api
249
-	 * @CalledIn SMF 2.1
250
-	 * @version 1.1
251
-	 * @since 1.1
252
-	 * @return void the passed $profile_areas is modified.
253
-	 */
254
-	public static function ProfileTrackSFS(int $memID): void
255
-	{
256
-		global $smcFunc;
257
-		$smcFunc['classSFS']->TrackSFS($memID);
258
-	}
259
-
260
-	/**
261
-	 * The caller for a profile check.
262
-	 *
263
-	 * @param int $memID The id of the member we are checking.
264
-	 *
265
-	 * @api
266
-	 * @CalledIn SMF 2.1
267
-	 * @version 1.1
268
-	 * @since 1.1
269
-	 * @return void the passed $profile_areas is modified.
270
-	 */
271
-	public function TrackSFS(int $memID): void
272
-	{
273
-		global $user_profile, $context, $smcFunc, $scripturl, $modSettings, $sourcedir;
274
-
275
-		isAllowedTo('moderate_forum');
276
-
277
-		// We need this stuff.
278
-		$context['sfs_allow_submit'] = !empty($modSettings['sfs_enablesubmission']) && !empty($modSettings['sfs_apikey']);
279
-		$context['token_check'] = 'sfs_submit-' . $memID;
280
-		$cache_key = 'sfs_check_member-' . $memID;
281
-
282
-		// Do we have a message?
283
-		$poster_name = null;
284
-		$poster_email = null;
285
-		$poster_ip = null;
286
-		$post_body = null;
287
-		if (isset($_GET['msg']) && intval($_GET['msg']) > 0)
288
-		{
289
-			$request = $smcFunc['db_query']('', '
290
-				SELECT poster_name, poster_email, poster_ip, body
291
-				FROM {db_prefix}messages
292
-				WHERE id_msg = {int:id_msg}
293
-					AND (
294
-						id_member = {int:id_member}
295
-						OR id_member = 0
296
-					)
297
-					AND {query_see_message_board}
298
-				',
299
-				array(
300
-					'id_msg' => (int) $_GET['msg'],
301
-					'id_member' => $memID,
302
-					'actor_is_admin' => $context['user']['is_admin'] ? 1 : 0
303
-				));
304
-			if ($smcFunc['db_num_rows']($request) == 1)
305
-			{
306
-				list($poster_name, $poster_email, $poster_ip, $post_body) = $smcFunc['db_fetch_row']($request);
307
-				$poster_ip = inet_dtop($poster_ip);
308
-			}
309
-			$smcFunc['db_free_result']($request);
310
-			
311
-			$context['reason'] = $smcFunc['htmlspecialchars']($post_body);
312
-		}
313
-		else
314
-			$context['reason'] = '';
315
-
316
-		// Are we submitting this?
317
-		if ($context['sfs_allow_submit'] && (isset($_POST['sfs_submit']) || isset($_POST['sfs_submitban'])))
318
-		{
319
-			checkSession();
320
-			if (!$this->versionCheck('2.0', 'smf'))
321
-				validateToken($context['token_check'], 'post');
322
-
323
-			$data = [
324
-				'username' => !empty($poster_name) ? $poster_name : $user_profile[$memID]['real_name'],
325
-				'email' => !empty($poster_email) ? $poster_email : $user_profile[$memID]['email_address'],
326
-				'ip_addr' => !empty($poster_ip) ? $poster_ip : $user_profile[$memID]['member_ip'],
327
-				'api_key' => $modSettings['sfs_apikey']
328
-			];
329
-			$post_data = http_build_query($data, '', '&');
330
-
331
-			// SMF 2.0 has the fetch_web_data in the Subs-Packages, 2.1 it is in Subs.php.
332
-			if ($this->versionCheck('2.0', 'smf'))
333
-				require_once($sourcedir . '/Subs-Package.php');
334
-
335
-			// Now we have a URL, lets go get it.
336
-			$result = fetch_web_data('https://www.stopforumspam.com/add', $post_data);
337
-
338
-			if (strpos($result, 'data submitted successfully') === false)
339
-				$context['submission_failed'] = $this->txt('sfs_submission_error');
340
-			else if (isset($_POST['sfs_submitban']))
341
-				redirectexit($scripturl . '?action=admin;area=ban;sa=add;u=' . $memID);
342
-			else
343
-				$context['submission_success'] = $this->txt('sfs_submission_success');
344
-		}
345
-	
346
-		// Check if we have this info.
347
-		if (($cache = cache_get_data($cache_key)) === null || ($response = $this->decodeJSON($cache, true)) === null)
348
-		{
349
-			$checks = [
350
-				['username' => $user_profile[$memID]['real_name']],
351
-				['email' => $user_profile[$memID]['email_address']],
352
-				['ip' => $user_profile[$memID]['member_ip']],
353
-				['ip' => $user_profile[$memID]['member_ip2']],
354
-			];
355
-
356
-			$requestURL = $this->buildServerURL();
357
-			$this->buildCheckPath($requestURL, $checks, 'profile');
358
-			$response = (array) $this->sendSFSCheck($requestURL, $checks, 'profile');
359
-		
360
-			cache_put_data($cache_key, $this->encodeJSON($response), 600);
361
-		}
362
-
363
-		// Prepare for the template.
364
-		$context['sfs_overall'] = (bool) $response['success'];
365
-		$context['sfs_checks'] = $response;
366
-		unset($context['sfs_checks']['success']);
367
-
368
-		if ($context['sfs_allow_submit'])
369
-		{
370
-			$context['sfs_submit_url'] = $scripturl . '?action=profile;area=sfs;u=' . $memID;
371
-			if (!$this->versionCheck('2.0', 'smf'))
372
-				createToken($context['token_check'], 'post');
373
-			else
374
-				unset($context['token_check']);
375
-		}
376
-
377
-		loadTemplate('StopForumSpam');
378
-		$context['sub_template'] = 'profile_tracksfs';
379
-	}
380
-
381
-	/**
382
-	 * Test for a standard post.
383
-	 *
384
-	 * @internal
385
-	 * @CalledIn SMF 2.0, SMF 2.1
386
-	 * @version 1.1
387
-	 * @since 1.1
388
-	 * @return bool True is success, no other bool is expeicifcly defined yet.
389
-	 */
390
-	private function checkVerificationTestPost(): bool
391
-	{
392
-		global $user_info, $modSettings;
393
-
394
-		// Guests!
395
-		if ($user_info['is_guest'])
396
-		{
397
-			$guestname = !isset($_POST['guestname']) ? '' : trim($_POST['guestname']);
398
-			$email = !isset($_POST['email']) ? '' : trim($_POST['email']);
399
-
400
-			return $this->sfsCheck(array(
401
-				array('username' => $guestname),
402
-				array('email' => $email),
403
-				array('ip' => $user_info['ip']),
404
-				array('ip' => $user_info['ip2']),
405
-			), 'post');
406
-
407
-		}
408
-		// Members and they don't have enough posts?
409
-		elseif (empty($user_info['posts']) || $user_info['posts'] < $modSettings['sfs_verfOptMemPostThreshold'])
410
-			return $this->sfsCheck(array(
411
-				array('username' => $user_info['username']),
412
-				array('email' => $user_info['email']),
413
-				array('ip' => $user_info['ip']),
414
-				array('ip' => $user_info['ip2']),
415
-			), 'post');
416
-		else
417
-			return true;
418
-	}
419
-
420
-	/**
421
-	 * Test for a report.
422
-	 *
423
-	 * @internal
424
-	 * @CalledIn SMF 2.0, SMF 2.1
425
-	 * @version 1.1
426
-	 * @since 1.1
427
-	 * @return bool True is success, no other bool is expeicifcly defined yet.
428
-	 */
429
-	private function checkVerificationTestReport(): bool
430
-	{
431
-		global $user_info;
432
-
433
-		$email = !isset($_POST['email']) ? '' : trim($_POST['email']);
434
-
435
-		return $this->sfsCheck(array(
436
-			array('email' => $email),
437
-			array('ip' => $user_info['ip']),
438
-			array('ip' => $user_info['ip2']),
439
-		), 'post');
440
-	}
441
-
442
-	/**
443
-	 * Test for a Search.
444
-	 *
445
-	 * @internal
446
-	 * @CalledIn SMF 2.0, SMF 2.1
447
-	 * @version 1.1
448
-	 * @since 1.1
449
-	 * @return bool True is success, no other bool is expeicifcly defined yet.
450
-	 */
451
-	private function checkVerificationTestSearch(): bool
452
-	{
453
-		global $user_info;
454
-
455
-		return $this->sfsCheck(array(
456
-			array('ip' => $user_info['ip']),
457
-			array('ip' => $user_info['ip2']),
458
-		), 'search');
459
-	}
460
-
461
-	/**
462
-	 * Test for extras, customizations and other areas that we want to tie in.
463
-	 *
464
-	 * @internal
465
-	 * @CalledIn SMF 2.0, SMF 2.1
466
-	 * @version 1.1
467
-	 * @since 1.1
468
-	 * @return bool True is success, no other bool is expeicifcly defined yet.
469
-	 */
470
-	private function checkVerificationTestExtra(array $thisVerification): bool
471
-	{
472
-		global $user_info;
473
-
474
-		foreach ($this->extraVerificationOptions as $option)
475
-		{
476
-			// Not a match.
477
-			if ($thisVerification['id'] != $option)
478
-				continue;
479
-
480
-			// Always try to send off IPs.
481
-			$checks = array(
482
-				array('ip' => $user_info['ip']),
483
-				array('ip' => $user_info['ip2']),
484
-			);
485
-
486
-			// Can we find a username?
487
-			$possibleUserNames = array('username', 'user_name', 'user', 'name', 'realname');
488
-			foreach ($possibleUserNames as $searchKey)
489
-				if (!empty($_POST[$searchKey]))
490
-				{
491
-					$checks[] = array('username' => $_POST[$searchKey]);
492
-					break;
493
-				}
494
-
495
-			// Can we find a email?
496
-			$possibleUserNames = array('email', 'emailaddress', 'email_address');
497
-			foreach ($possibleUserNames as $searchKey)
498
-				if (!empty($_POST[$searchKey]))
499
-				{
500
-					$checks[] = array('email' => $_POST[$searchKey]);
501
-					break;
502
-				}
503
-
504
-			return $this->sfsCheck($checks, $option);
505
-		}
506
-
507
-		return true;
508
-	}
509
-
510
-	/**
511
-	 * Run checks against the SFS database.
512
-	 *
513
-	 * @param array $checks All the possible checks we would like to preform.
514
-	 * @param string $area The area this is coming from.
515
-	 *
516
-	 * @internal
517
-	 * @CalledIn SMF 2.0, SMF 2.1
518
-	 * @version 1.2
519
-	 * @since 1.0
520
-	 * @return bool True is success, no other bool is expeicifcly defined yet.
521
-	 */
522
-	private function sfsCheck(array $checks, string $area = null): bool
523
-	{
524
-		global $smcFunc, $context, $modSettings;
525
-
526
-		$requestURL = $this->buildServerURL();
527
-
528
-		// Lets build our data set, always send it as a bulk.
529
-		$singleCheckFound = $this->buildCheckPath($requestURL, $checks, $area);
530
-
531
-		// No checks found? Can't do this.
532
-		if (empty($singleCheckFound))
533
-		{
534
-			$this->logAllStats('error', $checks, 'error');
535
-			log_error($this->txt('sfs_request_failure_nodata') . ':' . $requestURL, 'critical');
536
-			return true;
537
-		}
538
-
539
-		// Send it off.
540
-		$response = $this->sendSFSCheck($requestURL, $checks, $area);
541
-		$requestBlocked = '';
542
-
543
-		$checkMap = array(
544
-			'ip' => !empty($modSettings['sfs_ipcheck']) && !empty($response['ip']),
545
-			'username' => !empty($modSettings['sfs_usernamecheck']) && !empty($response['username']),
546
-			'email' => !empty($modSettings['sfs_emailcheck']) && !empty($response['email'])
547
-		);
548
-
549
-		// Are we requiring multiple checks.
550
-		if (!empty($modSettings['sfs_required']) && $modSettings['sfs_required'] != 'any')
551
-		{
552
-			// When requiring multiple checks, we require all to match.
553
-			$requiredChecks = explode('|', $modSettings['sfs_required']);
554
-			$result = true;
555
-			$test = '';
556
-			foreach ($requiredChecks as $key)
557
-			{
558
-				$test = call_user_func(array($this, 'sfsCheck_' . $key), $response[$key], $area);
559
-				$requestBlocked .= !empty($test) ? $test . '|' : '';
560
-				$result &= !empty($test);
561
-			}
562
-
563
-			// Not all checks passed, so we will allow it.
564
-			if (!$result)
565
-				$requestBlocked = '';			
566
-		}
567
-		// Otherwise we will check anything enabled and if any match, its found
568
-		else
569
-		{
570
-			foreach ($checkMap as $key => $checkEnabled)
571
-				if (empty($requestBlocked) && $checkEnabled)
572
-					$requestBlocked = call_user_func(array($this, 'sfsCheck_' . $key), $response[$key], $area);
573
-		}
574
-
575
-		// Log all the stats?  Debug mode here.
576
-		$this->logAllStats('all', $checks, $requestBlocked);
577
-
578
-		// At this point, we have checked everything, do what needs to be done for our good person.
579
-		if (empty($requestBlocked))
580
-			return true;
581
-
582
-		// You are a bad spammer, don't tell them what was blocked.
583
-		fatal_error($this->txt('sfs_request_blocked'));
584
-	}
585
-
586
-	/**
587
-	 * Send off the request to SFS and receive a response back
588
-	 *
589
-	 * @param string $requestURL The initial url we will send.
590
-	 * @param array $checks All the possible checks we would like to preform.
591
-	 * @param string $area The area this is coming from.
592
-	 *
593
-	 * @internal
594
-	 * @CalledIn SMF 2.0, SMF 2.1
595
-	 * @version 1.2
596
-	 * @since 1.1
597
-	 * @return array data we received back, could be a empty array.
598
-	 */
599
-	private function sendSFSCheck(string $requestURL, array $checks, string $area = null): array
600
-	{
601
-		global $sourcedir;
602
-
603
-		// SMF 2.0 has the fetch_web_data in the Subs-Packages, 2.1 it is in Subs.php.
604
-		if ($this->versionCheck('2.0', 'smf'))
605
-			require_once($sourcedir . '/Subs-Package.php');
606
-
607
-		// Now we have a URL, lets go get it.
608
-		$result = fetch_web_data($requestURL);
609
-		if ($result === false)
610
-		{
611
-			$this->logAllStats('error', $checks, 'failure');
612
-			log_error($this->txt('sfs_request_failure') . ':' . $requestURL, 'critical');
613
-			return true;
614
-		}
615
-
616
-		$response = $this->decodeJSON($result);
617
-
618
-		// No data received, log it and let them through.
619
-		if (empty($response))
620
-		{
621
-			$this->logAllStats('error', $checks, 'failure');
622
-			log_error($this->txt('sfs_request_failure') . ':' . $requestURL, 'critical');
623
-			return true;
624
-		}
625
-
626
-		return $response;
627
-	}
628
-
629
-	/**
630
-	 * Run checks for IPs
631
-	 *
632
-	 * @param array $ips All the IPs we are checking.
633
-	 * @param string $area If defined the area we are checking.
634
-	 * @internal
635
-	 * @CalledIn SMF 2.0, SMF 2.1
636
-	 * @version 1.2
637
-	 * @since 1.1
638
-	 * @return string Request Blocked data if any
639
-	 */
640
-	private function sfsCheck_ip(array $ips, string $area = ''): string
641
-	{
642
-		global $modSettings, $smcFunc;
643
-
644
-		$requestBlocked = '';
645
-		foreach ($ips as $check)
646
-		{
647
-			// They appeared! Block this.
648
-			if (empty($check['appears']))
649
-				continue;
650
-
651
-			// Ban them because they are black listed?
652
-			$autoBlackListResult = '0';
653
-			if (!empty($modSettings['sfs_ipcheck_autoban']) && !empty($check['frequency']) && $check['frequency'] == 255)
654
-				$autoBlackListResult = $this->BanNewIP($check['value']);
655
-
656
-			$this->logBlockedStats('ip', $check);
657
-			$requestBlocked = 'ip,' . $smcFunc['htmlspecialchars']($check['value']) . ',' . ($autoBlackListResult ? 1 : 0);
658
-			break;
659
-		}
660
-
661
-		return $requestBlocked;
662
-	}
663
-
664
-	/**
665
-	 * Run checks for Usernames
666
-	 *
667
-	 * @param array $usernames All the usernames we are checking.
668
-	 * @param string $area If defined the area we are checking.
669
-	 * @internal
670
-	 * @CalledIn SMF 2.0, SMF 2.1
671
-	 * @version 1.2
672
-	 * @since 1.1
673
-	 * @return string Request Blocked data if any
674
-	 */
675
-	private function sfsCheck_username(array $usernames, string $area = ''): string
676
-	{
677
-		global $modSettings, $smcFunc;
678
-
679
-		$requestBlocked = '';
680
-		foreach ($usernames as $check)
681
-		{
682
-			// Combine with $area we could also require admin approval above thresholds on things like register.
683
-			if (empty($check['appears']))
684
-				continue;
685
-
686
-			$shouldBlock = true;
687
-
688
-			// We are not confident that they should be blocked.
689
-			if (!empty($modSettings['sfs_username_confidence']) && !empty($check['confidence']) && $area == 'register' && (float) $modSettings['sfs_username_confidence'] > (float) $check['confidence'])
690
-			{
691
-				$this->logAllStats('all', $check, 'username,' . $smcFunc['htmlspecialchars']($check['value']) . ',' . $check['confidence']);
692
-				$shouldBlock = false;
693
-			}
694
-
695
-			// Block them.
696
-			if ($shouldBlock)
697
-			{
698
-				$this->logBlockedStats('username', $check);
699
-				$requestBlocked = 'username,' . $smcFunc['htmlspecialchars']($check['value']) . ',' . $check['confidence'];
700
-				break;
701
-			}
702
-		}
703
-
704
-		return $requestBlocked;
705
-	}
706
-
707
-	/**
708
-	 * Run checks for Email
709
-	 *
710
-	 * @param array $email All the email we are checking.
711
-	 * @param string $area If defined the area we are checking.
712
-	 * @internal
713
-	 * @CalledIn SMF 2.0, SMF 2.1
714
-	 * @version 1.2
715
-	 * @since 1.1
716
-	 * @return string Request Blocked data if any
717
-	 */
718
-	private function sfsCheck_email(array $email, string $area = ''): string
719
-	{
720
-		global $modSettings, $smcFunc;
721
-
722
-		$requestBlocked = '';
723
-		foreach ($email as $check)
724
-		{
725
-			if (empty($check['appears']))
726
-				continue;
727
-
728
-			$this->logBlockedStats('email', $check);
729
-			$requestBlocked = 'email,' . $smcFunc['htmlspecialchars']($check['value']);
730
-			break;
731
-		}
732
-
733
-		return $requestBlocked;
734
-	}
735
-
736
-	/**
737
-	 * Run checks against the SFS database.
738
-	 *
739
-	 * @param string $requestURL The initial url we will send.
740
-	 * @param array $checks All the possible checks we would like to preform.
741
-	 * @param string $area The area this is coming from.
742
-	 *
743
-	 * @internal
744
-	 * @CalledIn SMF 2.0, SMF 2.1
745
-	 * @version 1.2
746
-	 * @since 1.0
747
-	 * @return bool True we found something to check, false nothing..  $requestURL will be updated with the new data.
748
-	 */
749
-	private function buildCheckPath(string &$requestURL, array $checks, string $area = null): bool
750
-	{
751
-		global $context, $modSettings;
752
-
753
-		$singleCheckFound = false;
754
-		foreach ($checks as $chk)
755
-		{
756
-			foreach ($chk as $type => $value)
757
-			{
758
-				// Hold up, we are not processing this check.
759
-				if (in_array($type, array('email', 'username', 'ip')) && empty($modSettings['sfs_' . $type . 'check']))
760
-					continue;
761
-
762
-				// No value? Can't do this.
763
-				if (empty($value))
764
-					continue;
765
-
766
-				// Emails and usernames must be UTF-8, Only a issue with SMF 2.0.
767
-				if (!$context['utf8'] && ($type == 'email' || $type == 'username'))
768
-					$requestURL .= '&' . $type . '[]=' . iconv($context['character_set'], 'UTF-8//IGNORE', $value);
769
-				else
770
-					$requestURL .= '&' . $type . '[]=' . urlencode($value);
771
-
772
-				$singleCheckFound = true;
773
-			}
774
-		}
775
-
776
-		return $singleCheckFound;
777
-	}
778
-
779
-	/**
780
-	 * Log that this was blocked.
781
-	 *
782
-	 * @param string $type Either username, email, or ip.  Anything else gets marked uknown.
783
-	 * @param array $check The check data we are logging.
784
-	 *
785
-	 * @internal
786
-	 * @CalledIn SMF 2.0, SMF 2.1
787
-	 * @version 1.0
788
-	 * @since 1.0
789
-	 * @return bool True is success, no other bool is expeicifcly defined yet.
790
-	 */
791
-	private function logBlockedStats(string $type, array $check): void
792
-	{
793
-		global $smcFunc, $user_info;
794
-
795
-		// What type of log is this?
796
-		$blockType = isset($this->blockTypeMap[$type]) ? $this->blockTypeMap[$type] : 99;
797
-
798
-		$smcFunc['db_insert']('',
799
-			'{db_prefix}log_sfs',
800
-			array(
801
-				'id_type' => 'int',
802
-				'log_time' => 'int',
803
-				'url' => 'string',
804
-				'id_member' => 'int',
805
-				'username' => 'string',
806
-				'email' => 'string',
807
-				'ip' => 'string',
808
-				'ip2' => 'string',
809
-				'checks' => 'string',
810
-				'result' => 'string'
811
-			),
812
-			array(
813
-				$blockType, // Blocked request
814
-				time(),
815
-				$smcFunc['htmlspecialchars']($_SERVER['REQUEST_URL']),
816
-				$user_info['id'],
817
-				$type == 'username' ? $check['value'] : '',
818
-				$type == 'email' ? $check['value'] : '',
819
-				$type == 'ip' ? $check['value'] : $user_info['ip'],
820
-				$user_info['ip2'],
821
-				$this->encodeJSON($check),
822
-				'Blocked'
823
-				),
824
-			array('id_sfs', 'id_type')
825
-		);
826
-	}
827
-
828
-	/**
829
-	 * Debug logging that this was blocked..
830
-	 *
831
-	 * @param string $type Either error or all, currently ignored.
832
-	 * @param array $check The check data we are logging.
833
-	 * @param string $DebugMessage Debugging message, sometimes just is error or failure, otherwise a comma separated of what request was blocked.
834
-	 *
835
-	 * @internal
836
-	 * @CalledIn SMF 2.0, SMF 2.1
837
-	 * @version 1.2
838
-	 * @since 1.0
839
-	 * @return bool True is success, no other bool is expeicifcly defined yet.
840
-	 */
841
-	private function logAllStats(string $type, array $checks, string $DebugMessage): void
842
-	{
843
-		global $modSettings, $smcFunc, $user_info;
844
-
845
-		if ($type == 'all' && empty($modSettings['sfs_log_debug']))
846
-			return;
847
-
848
-		$smcFunc['db_insert']('',
849
-			'{db_prefix}log_sfs',
850
-			array(
851
-				'id_type' => 'int',
852
-				'log_time' => 'int',
853
-				'url' => 'string',
854
-				'id_member' => 'int',
855
-				'username' => 'string',
856
-				'email' => 'string',
857
-				'ip' => 'string',
858
-				'ip2' => 'string',
859
-				'checks' => 'string',
860
-				'result' => 'string'
861
-			),
862
-			array(
863
-				0, // Debug type.
864
-				time(),
865
-				$smcFunc['htmlspecialchars']($_SERVER['REQUEST_URL']),
866
-				$user_info['id'],
867
-				'', // Username
868
-				'', // email
869
-				$user_info['ip'],
870
-				$user_info['ip2'],
871
-				json_encode($checks),
872
-				$DebugMessage,
873
-				),
874
-			array('id_sfs', 'id_type')
875
-		);
876
-	}
877
-
878
-	/**
879
-	 * Decode JSON data and return it.
880
-	 * If we have $smcFunc['json_decode'], we use this as it handles errors natively.
881
-	 * For all others, we simply ensure a proper array is returned in the event of a error.
882
-	 *
883
-	 * @param string $requestData A properly formatted json string.
884
-	 *
885
-	 * @internal
886
-	 * @CalledIn SMF 2.0, SMF 2.1
887
-	 * @version 1.0
888
-	 * @since 1.0
889
-	 * @return array The parsed json string is now an array.
890
-	 */
891
-	public function decodeJSON(string $requestData): array
892
-	{
893
-		global $smcFunc;
894
-
895
-		// Do we have $smcFunc?  It handles errors and logs them as needed.
896
-		if (isset($smcFunc['json_decode']) && is_callable($smcFunc['json_decode']))
897
-			return $smcFunc['json_decode']($requestData, true);
898
-		// Back to the basics.
899
-		else
900
-		{
901
-			$data = @json_decode($requestData, true);
902
-
903
-			// We got a error, return nothing.  Don't log this, not worth it.
904
-			if (json_last_error() !== JSON_ERROR_NONE)
905
-				return array();
906
-			return $data;
907
-		}
908
-	}
909
-
910
-	/**
911
-	 * json JSON data and return it.
912
-	 * If we have $smcFunc['json_encode'], we use this as it handles errors natively.
913
-	 * For all others, we simply ensure a proper array is returned in the event of a error.
914
-	 *
915
-	 * @param array $requestData A properly formatted json string.
916
-	 *
917
-	 * @internal
918
-	 * @CalledIn SMF 2.0, SMF 2.1
919
-	 * @version 1.1
920
-	 * @since 1.1
921
-	 * @return string The stringified array.
922
-	 */
923
-	public function encodeJSON(array $requestData): string
924
-	{
925
-		global $smcFunc;
926
-
927
-		// Do we have $smcFunc?  It handles errors and logs them as needed.
928
-		if (isset($smcFunc['json_encode']) && is_callable($smcFunc['json_encode']))
929
-			return $smcFunc['json_encode']($requestData);
930
-		// Back to the basics.
931
-		else
932
-		{
933
-			$data = @json_encode($requestData);
934
-
935
-			// We got a error, return nothing.  Don't log this, not worth it.
936
-			if (json_last_error() !== JSON_ERROR_NONE)
937
-				return null;
938
-			return $data;
939
-		}
940
-	}
941
-
942
-	/**
943
-	 * Build the SFS Server URL based on our configuration setup.
944
-	 *
945
-	 * @internal
946
-	 * @link: https://www.stopforumspam.com/usage
947
-	 * @CalledIn SMF 2.0, SMF 2.1
948
-	 * @version 1.2
949
-	 * @since 1.0
950
-	 * @return array The parsed json string is now an array.
951
-	 */
952
-	private function buildServerURL(): string
953
-	{
954
-		global $modSettings;
955
-		static $url = null;
956
-
957
-		// If we build this once, don't do it again.
958
-		if (!empty($url))
959
-			return $url;
960
-
961
-		// Get our server info.
962
-		$server = $this->sfsServerMapping()[$modSettings['sfs_region']];
963
-
964
-		// Build the base URL, we always use json responses.
965
-		$url = 'https://' . $server['host'] . '/api?json';
966
-
967
-		// All the SFS Urls => How we toggle them.
968
-		$sfsMap = array(
969
-			'nobadall' => !empty($modSettings['sfs_wildcard_email']) && !empty($modSettings['sfs_wildcard_username']) && !empty($modSettings['sfs_wildcard_ip']),
970
-			'notorexit' => !empty($modSettings['sfs_tor_check']) && $modSettings['sfs_tor_check'] == 1,
971
-			'badtorexit' => !empty($modSettings['sfs_tor_check']) && $modSettings['sfs_tor_check'] == 2,
972
-		);
973
-		foreach ($sfsMap as $val => $key)
974
-			if (!empty($key))
975
-				$url .= '&' . $val;
976
-
977
-		// Maybe only certain wildcards are ignored?
978
-		if (empty($sfsMap['nobadall']))
979
-		{
980
-			$ignoreMap = array(
981
-				'nobadusername' => !empty($modSettings['sfs_wildcard_email']),
982
-				'nobademail' => !empty($modSettings['sfs_wildcard_username']),
983
-				'nobadip' => !empty($modSettings['sfs_wildcard_ip']),
984
-			);
985
-
986
-			foreach ($ignoreMap as $val => $key)
987
-				if (!empty($key))
988
-					$url .= '&' . $val;
989
-		}
990
-
991
-		// Do we have to filter out from lastseen?
992
-		if (!empty($modSettings['sfs_expire']))
993
-			$url .= '&expire=' . (int) $modSettings['sfs_expire'];
994
-
995
-		return $url;
996
-	}
997
-
998
-	/**
999
-	 * Setup our possible SFS hosts.
1000
-	 *
1001
-	 * @internal
1002
-	 * @link: https://www.stopforumspam.com/usage
1003
-	 * @CalledIn SMF 2.0, SMF 2.1
1004
-	 * @version 1.0
1005
-	 * @since 1.0
1006
-	 * @return array The list of servers.
1007
-	 */
1008
-	public function sfsServerMapping($returnType = null)
1009
-	{
1010
-		// Global list of servers.
1011
-		$serverList = array(
1012
-			0 => array(
1013
-				'region' => 'global',
1014
-				'label' => $this->txt('sfs_region_global'),
1015
-				'host' => 'api.stopforumspam.org',
1016
-			),
1017
-			1 => array(
1018
-				'region' => 'us',
1019
-				'label' => $this->txt('sfs_region_us'),
1020
-				'host' => 'us.stopforumspam.org',
1021
-			),
1022
-			2 => array(
1023
-				'region' => 'eu',
1024
-				'label' => $this->txt('sfs_region_eu'),
1025
-				'host' => 'eruope.stopforumspam.org',
1026
-			),
1027
-		);
1028
-
1029
-		// Configs only need the labels.
1030
-		if ($returnType == 'config')
1031
-		{
1032
-			$temp = array();
1033
-			foreach ($serverList as $id_server => $server)
1034
-				$temp[$id_server] = $server['label'];
1035
-			return $temp;
1036
-		}
1037
-
1038
-		return $serverList;
1039
-	}
1040
-
1041
-	/**
1042
-	 * Our possible verification options.
1043
-	 *
1044
-	 * @internal
1045
-	 * @CalledIn SMF 2.0, SMF 2.1
1046
-	 * @version 1.0
1047
-	 * @since 1.0
1048
-	 * @return array The list of servers.
1049
-	 */
1050
-	private function getVerificationOptions(): array
1051
-	{
1052
-		global $user_info, $modSettings;
1053
-
1054
-		$optionsKey = $user_info['is_guest'] ? 'sfs_verification_options' : 'sfs_verOptionsMembers';
1055
-		$optionsKeyExtra = $user_info['is_guest'] ? 'sfs_verification_options_extra' : 'sfs_verOptionsMemExtra';
1056
-
1057
-		// Standard options.
1058
-		if ($this->versionCheck('2.0', 'smf') && !empty($modSettings[$optionsKey]))
1059
-			$options = safe_unserialize($modSettings[$optionsKey]);
1060
-		elseif (!empty($modSettings[$optionsKey]))
1061
-			$options = $this->decodeJSON($modSettings[$optionsKey]);
1062
-
1063
-		if (empty($options))
1064
-			$options = array();
1065
-
1066
-		// Extras.
1067
-		if (!empty($modSettings[$optionsKeyExtra]))
1068
-		{
1069
-			$this->extraVerificationOptions = explode(',', $modSettings[$optionsKeyExtra]);
1070
-
1071
-			if (!empty($this->extraVerificationOptions))
1072
-				$options = array_merge($options, $this->extraVerificationOptions);
1073
-		}
1074
-
1075
-		return $options;
1076
-	}
1077
-
1078
-	/**
1079
-	 * Our possible default options.
1080
-	 * We don't specify them all, just ones that make sense for code development.
1081
-	 *
1082
-	 * @param bool $undo If true, we reverse any defaults we set.  Makes the admin page work.
1083
-	 *
1084
-	 * @internal
1085
-	 * @CalledIn SMF 2.0, SMF 2.1
1086
-	 * @version 1.0
1087
-	 * @since 1.0
1088
-	 * @return void Nothing is returned, we inject into $modSettings.
1089
-	 */
1090
-	public function loadDefaults($undo = false)
1091
-	{
1092
-		global $modSettings;
1093
-
1094
-		// Specify the defaults, but only non empties.
1095
-		$defaultSettings = array(
1096
-			'sfs_enabled' => 1,
1097
-			'sfs_expire' => 90,
1098
-			'sfs_emailcheck' => 1,
1099
-			'sfs_username_confidence' => 50.01,
1100
-			'sfs_region' => 0,
1101
-			'sfs_verfOptMemPostThreshold' => 5
1102
-		);
1103
-
1104
-		// SMF 2.0 is serialized, SMF 2.1 is json.
1105
-		$encodeFunc = 'json_encode';
1106
-		if ($this->versionCheck('2.0', 'smf'))
1107
-			$encodeFunc = 'serialize';
1108
-
1109
-		$defaultSettings['sfs_verification_options'] = $encodeFunc(array('post'));
1110
-
1111
-		// We undoing this? Maybe a save?
1112
-		if ($undo)
1113
-		{
1114
-			foreach ($this->changedSettings as $key => $value)
1115
-				unset($modSettings[$key], $this->changedSettings[$key]);
1116
-			return true;
1117
-		}
1118
-
1119
-		// Enabled settings.
1120
-		foreach ($defaultSettings as $key => $value)
1121
-			if (!isset($modSettings[$key]))
1122
-			{
1123
-				$this->changedSettings[$key] = null;
1124
-				$modSettings[$key] = $value;
1125
-			}
1126
-
1127
-		return true;
1128
-	}
1129
-
1130
-	/**
1131
-	 * We undo the defaults letting us save the admin page properly.
1132
-	 *
1133
-	 * @internal
1134
-	 * @CalledIn SMF 2.0, SMF 2.1
1135
-	 * @version 1.0
1136
-	 * @since 1.0
1137
-	 * @return void Nothing is returned, we inject into $modSettings.
1138
-	 */
1139
-	public function unloadDefaults()
1140
-	{
1141
-		return $this->loadDefaults(true);
1142
-	}
1143
-
1144
-	/**
1145
-	 * Checks if we are matching an array of versions against a specific version.
1146
-	 *
1147
-	 * @param string|array $version The version to check, this is converted to an array later on.
1148
-	 * @param string $software The software we are matching against.
1149
-	 *
1150
-	 * @internal
1151
-	 * @CalledIn SMF 2.0, SMF 2.1
1152
-	 * @version 1.0
1153
-	 * @since 1.0
1154
-	 * @return bool True if we matched a version, false otherwise.
1155
-	 */
1156
-	public function versionCheck($version, string $software = 'smf'): bool
1157
-	{
1158
-		// We can't do this if the software doesn't match.
1159
-		if ($software !== $this->softwareName)
1160
-			return false;
1161
-
1162
-		// Allow multiple versions to pass.
1163
-		$version = (array) $version;
1164
-		foreach ($version as $v)
1165
-			if ($v == $this->softwareVersion)
1166
-				return true;
1167
-
1168
-		// No match? False.
1169
-		return false;
1170
-	}
1171
-
1172
-	/**
1173
-	 * A global function for loading our lanague up.
1174
-	 * Placeholder to allow easier additional loading or other software/versions to change this as needed.
1175
-	 *
1176
-	 * @internal
1177
-	 * @CalledIn SMF 2.0, SMF 2.1
1178
-	 * @version 1.0
1179
-	 * @since 1.0
1180
-	 * @return void No return is generated here.
1181
-	 */
1182
-	public function loadLanguage(): void
1183
-	{
1184
-		// Load the langauge.
1185
-		loadLanguage('StopForumSpam');
1186
-	}
1187
-
1188
-	/**
1189
-	 * A global function for loading $txt strings.
1190
-	 *
1191
-	 * @param string $key The key of the text string we want to load.
1192
-	 * @internal
1193
-	 * @CalledIn SMF 2.0, SMF 2.1
1194
-	 * @version 1.1
1195
-	 * @since 1.1
1196
-	 * @return string The text string.
1197
-	 */
1198
-	public function txt($key): string
1199
-	{
1200
-		global $txt;
1201
-
1202
-		// Load the language if its not here already.
1203
-		if (!isset($txt[$key]))
1204
-			$this->loadLanguage();
1205
-
1206
-		if (!isset($txt[$key]))
1207
-			return '';
1208
-
1209
-		return $txt[$key];
1210
-	}
1211
-
1212
-	/**
1213
-	 * Create a Ban Group if needed to handle automatic IP bans.
1214
-	 * We attempt to use the known ban function to create bans, otherwise we just fall back to a standard insert.
1215
-	 *
1216
-	 * @internal
1217
-	 * @CalledIn SMF 2.0, SMF 2.1
1218
-	 * @version 1.2
1219
-	 * @since 1.0
1220
-	 * @return bool True upon success, false otherwise.
1221
-	 */
1222
-	public function createBanGroup(bool $noChecks = false): bool
1223
-	{
1224
-		global $smcFunc, $modSettings, $sourcedir, $txt;
1225
-
1226
-		// Is this disabled? Don't do it.
1227
-		if (empty($noChecks) && empty($modSettings['sfs_ipcheck_autoban']))
1228
-			return false;
1229
-
1230
-		// Maybe just got unlinked, if we can find the matching name, relink it.
1231
-		$request = $smcFunc['db_query']('', '
1232
-			SELECT id_ban_group
1233
-			FROM {db_prefix}ban_groups
1234
-			WHERE name = {string:new_ban_name}
1235
-			LIMIT 1',
1236
-			array(
1237
-				'new_ban_name' => substr($this->txt('sfs_ban_group_name'), 0, 20),
1238
-			)
1239
-		);
1240
-		if ($smcFunc['db_num_rows']($request) == 1)
1241
-		{
1242
-			$ban_data = $smcFunc['db_fetch_assoc']($request);
1243
-			$smcFunc['db_free_result']($request);
1244
-
1245
-			if (!empty($ban_data['id_ban_group']))
1246
-			{
1247
-				updateSettings(array('sfs_ipcheck_autoban_group' => $ban_data['id_ban_group']));
1248
-				return true;
1249
-			}
1250
-		}
1251
-		$smcFunc['db_free_result']($request);
1252
-
1253
-		require_once($sourcedir . '/ManageBans.php');
1254
-
1255
-		// Ban Information, this follows the format from the function.
1256
-		$ban_info = array(
1257
-			'name' => substr($this->txt('sfs_ban_group_name'), 0, 20),
1258
-			'cannot' => array(
1259
-				'access' => 1,
1260
-				'register' => 1,
1261
-				'post' => 1,
1262
-				'login' => 1,
1263
-			),
1264
-			'db_expiration' => 'NULL',
1265
-			'reason' => $this->txt('sfs_ban_group_reason'),
1266
-			'notes' => $this->txt('sfs_ban_group_notes')
1267
-		);
1268
-
1269
-		// If we can shortcut this..
1270
-		$ban_group_id = 0;
1271
-		if (function_exists('insertBanGroup'))
1272
-			$ban_group_id = insertBanGroup($ban_info);
1273
-
1274
-		// Fall back.
1275
-		if (is_array($ban_group_id) || empty($ban_group_id))
1276
-			$ban_group_id = $this->createBanGroupDirect($ban_info);
1277
-
1278
-		// Didn't work? Try again later.
1279
-		if (empty($ban_group_id))
1280
-			return false;
1281
-
1282
-		updateSettings(array('sfs_ipcheck_autoban_group' => $ban_group_id));
1283
-		return true;
1284
-	}
1285
-
1286
-	/**
1287
-	 * We failed to create a ban group via the API, do it manually.
1288
-	 *
1289
-	 * @param array $ban_info The ban info
1290
-	 *
1291
-	 * @internal
1292
-	 * @CalledIn SMF 2.0, SMF 2.1
1293
-	 * @version 1.2
1294
-	 * @since 1.2
1295
-	 * @return bool True upon success, false otherwise.
1296
-	 */
1297
-	public function createBanGroupDirect(array $ban_info): int
1298
-	{
1299
-		global $smcFunc;
1300
-
1301
-		$smcFunc['db_insert']('',
1302
-			'{db_prefix}ban_groups',
1303
-			array(
1304
-				'name' => 'string-20', 'ban_time' => 'int', 'expire_time' => 'raw', 'cannot_access' => 'int', 'cannot_register' => 'int',
1305
-				'cannot_post' => 'int', 'cannot_login' => 'int', 'reason' => 'string-255', 'notes' => 'string-65534',
1306
-			),
1307
-			array(
1308
-				$ban_info['name'], time(), $ban_info['db_expiration'], $ban_info['cannot']['access'], $ban_info['cannot']['register'],
1309
-				$ban_info['cannot']['post'], $ban_info['cannot']['login'], $ban_info['reason'], $ban_info['notes'],
1310
-			),
1311
-			array('id_ban_group'),
1312
-			1
1313
-		);
1314
-		return $smcFunc['db_insert_id']('{db_prefix}ban_groups', 'id_ban_group');
1315
-	}
1316
-
1317
-	/**
1318
-	 * They have triggered a automatic IP ban, lets do it.
1319
-	 * In newer versions we attempt to use more of the APIs, but fall back as needed.
1320
-	 *
1321
-	 * @param string $ip_address The IP address of the spammer.
1322
-	 *
1323
-	 * @internal
1324
-	 * @CalledIn SMF 2.0, SMF 2.1
1325
-	 * @version 1.2
1326
-	 * @since 1.0
1327
-	 * @return bool True upon success, false otherwise.
1328
-	 */
1329
-	private function BanNewIP(string $ip_address): bool
1330
-	{
1331
-		global $smcFunc, $modSettings, $sourcedir;
1332
-
1333
-		// Did we loose our Ban Group? Try to fix this.
1334
-		if (!empty($modSettings['sfs_ipcheck_autoban']) && empty($modSettings['sfs_ipcheck_autoban_group']))
1335
-			$this->createBanGroup();
1336
-
1337
-		// Still no Ban Group? Bail out.
1338
-		if (empty($modSettings['sfs_ipcheck_autoban']) || empty($modSettings['sfs_ipcheck_autoban_group']))
1339
-			return false;
1340
-
1341
-		require_once($sourcedir . '/ManageBans.php');
1342
-
1343
-		// If we have it, use the standard function.
1344
-		if (function_exists('addTriggers'))
1345
-			$result = $this->BanNewIPSMF21($ip_address);
1346
-		// Go old school.
1347
-		else
1348
-			$result = $this->BanNewIPSMF20($ip_address);
1349
-
1350
-		// Did this work?
1351
-		if ($result)
1352
-		{
1353
-			// Log this.  The log will show from the user/guest and ip of spammer.
1354
-			logAction('ban', array(
1355
-				'ip_range' => $ip_address,
1356
-				'new' => 1,
1357
-				'source' => 'sfs'
1358
-			));
1359
-
1360
-			// Let things know we need updated ban data.
1361
-			updateSettings(array('banLastUpdated' => time()));
1362
-			updateBanMembers();
1363
-		}
1364
-
1365
-		return true;
1366
-	}
1367
-
1368
-	/**
1369
-	 * Ban a IP with using some functions that exist in SMF 2.1.
1370
-	 *
1371
-	 * @param string $ip_address The IP address of the spammer.
1372
-	 *
1373
-	 * @internal
1374
-	 * @CalledIn SMF 2.0
1375
-	 * @version 1.2
1376
-	 * @since 1.2
1377
-	 * @return bool True upon success, false otherwise.
1378
-	 */
1379
-	private function BanNewIPSMF21(string $ip_address): bool
1380
-	{
1381
-		global $smcFunc, $modSettings;
1382
-
1383
-		// We don't call checkExistingTriggerIP as it induces a fatal error.
1384
-		$request = $smcFunc['db_query']('', '
1385
-			SELECT bg.id_ban_group, bg.name
1386
-			FROM {db_prefix}ban_groups AS bg
1387
-			INNER JOIN {db_prefix}ban_items AS bi ON
1388
-				(bi.id_ban_group = bg.id_ban_group)
1389
-				AND ip_low = {inet:ip_low} AND ip_high = {inet:ip_high}
1390
-			LIMIT 1',
1391
-			array(
1392
-				'ip_low' => $ip_address,
1393
-				'ip_high' => $ip_address,
1394
-			)
1395
-		);
1396
-		// Alredy exists, bail out.
1397
-		if ($smcFunc['db_num_rows']($request) != 0)
1398
-		{
1399
-			$smcFunc['db_free_result']($request);
1400
-			return false;
1401
-		}
1402
-
1403
-		// The trigger info.
1404
-		$triggers = array(
1405
-			array(
1406
-				'ip_low' => $ip_address,
1407
-				'ip_high' => $ip_address,
1408
-			)
1409
-		);
1410
-
1411
-		// Add it.
1412
-		addTriggers($modSettings['sfs_ipcheck_autoban_group'], $triggers);
1413
-
1414
-		return true;
1415
-	}
1416
-
1417
-	/**
1418
-	 * We need to fall back to standard db inserts to ban a user as the functions don't exist.
1419
-	 *
1420
-	 * @param string $ip_address The IP address of the spammer.
1421
-	 *
1422
-	 * @internal
1423
-	 * @CalledIn SMF 2.0
1424
-	 * @version 1.2
1425
-	 * @since 1.2
1426
-	 * @return bool True upon success, false otherwise.
1427
-	 */
1428
-	private function BanNewIPSMF20(string $ip_address): bool
1429
-	{
1430
-		global $smcFunc, $modSettings;
1431
-
1432
-		$ip_parts = ip2range($ip_address);
1433
-
1434
-		// Not valid? Get out.
1435
-		if (count($ip_parts) != 4)
1436
-			return false;
1437
-
1438
-		// We don't call checkExistingTriggerIP as it induces a fatal error.
1439
-		$request = $smcFunc['db_query']('', '
1440
-			SELECT bg.id_ban_group, bg.name
1441
-			FROM {db_prefix}ban_groups AS bg
1442
-			INNER JOIN {db_prefix}ban_items AS bi ON
1443
-				(bi.id_ban_group = bg.id_ban_group)
1444
-				AND ip_low1 = {int:ip_low1} AND ip_high1 = {int:ip_high1}
1445
-				AND ip_low2 = {int:ip_low2} AND ip_high2 = {int:ip_high2}
1446
-				AND ip_low3 = {int:ip_low3} AND ip_high3 = {int:ip_high3}
1447
-				AND ip_low4 = {int:ip_low4} AND ip_high4 = {int:ip_high4}
1448
-			LIMIT 1',
1449
-			array(
1450
-				'ip_low1' => $ip_parts[0]['low'],
1451
-				'ip_high1' => $ip_parts[0]['high'],
1452
-				'ip_low2' => $ip_parts[1]['low'],
1453
-				'ip_high2' => $ip_parts[1]['high'],
1454
-				'ip_low3' => $ip_parts[2]['low'],
1455
-				'ip_high3' => $ip_parts[2]['high'],
1456
-				'ip_low4' => $ip_parts[3]['low'],
1457
-				'ip_high4' => $ip_parts[3]['high'],
1458
-			)
1459
-		);
1460
-		// Alredy exists, bail out.
1461
-		if ($smcFunc['db_num_rows']($request) != 0)
1462
-		{
1463
-			$smcFunc['db_free_result']($request);
1464
-			return false;
1465
-		}
1466
-
1467
-		$ban_triggers = array(array(
1468
-			$modSettings['sfs_ipcheck_autoban_group'],
1469
-			$ip_parts[0]['low'],
1470
-			$ip_parts[0]['high'],
1471
-			$ip_parts[1]['low'],
1472
-			$ip_parts[1]['high'],
1473
-			$ip_parts[2]['low'],
1474
-			$ip_parts[2]['high'],
1475
-			$ip_parts[3]['low'],
1476
-			$ip_parts[3]['high'],
1477
-			'',
1478
-			'',
1479
-			0,
1480
-		));
1481
-
1482
-		$smcFunc['db_insert']('',
1483
-			'{db_prefix}ban_items',
1484
-			array(
1485
-				'id_ban_group' => 'int', 'ip_low1' => 'int', 'ip_high1' => 'int', 'ip_low2' => 'int', 'ip_high2' => 'int',
1486
-				'ip_low3' => 'int', 'ip_high3' => 'int', 'ip_low4' => 'int', 'ip_high4' => 'int', 'hostname' => 'string-255',
1487
-				'email_address' => 'string-255', 'id_member' => 'int',
1488
-			),
1489
-			$ban_triggers,
1490
-			array('id_ban')
1491
-		);
1492
-
1493
-		return true;
1494
-	}
1495
-
1496
-	/**
1497
-	 * The caller for a test check.
1498
-	 *
1499
-	 * @param array $checks The data we are checking.
1500
-	 *
1501
-	 * @api
1502
-	 * @CalledIn SMF 2.0, SMF 2.1
1503
-	 * @version 1.4.0
1504
-	 * @since 1.4.0
1505
-	 * @return array The results of the check.
1506
-	 */
1507
-	public function TestSFS(array $checks): array
1508
-	{
1509
-		$requestURL = $this->buildServerURL();
1510
-
1511
-		// Lets build our data set, always send it as a bulk.
1512
-		$singleCheckFound = $this->buildCheckPath($requestURL, $checks, 'test');
1513
-
1514
-		// No checks found? Can't do this.
1515
-		if (empty($singleCheckFound))
1516
-			return [];
1517
-
1518
-		// Send this off.
1519
-		return $this->sendSFSCheck($requestURL, $checks, 'test');
1520
-	}
1521
-
1522
-	/**
1523
-	 * Get some data
1524
-	 *
1525
-	 * @param string $variable The data we are looking for..
1526
-	 *
1527
-	 * @internal
1528
-	 * @CalledIn SMF 2.0, SMF 2.1
1529
-	 * @version 1.2
1530
-	 * @since 1.2
1531
-	 * @return bool True upon success, false otherwise.
1532
-	 */
1533
-	public function get(string $variable)
1534
-	{
1535
-		if (in_array($variable, array('softwareName', 'softwareVersion')))
1536
-			return $this->{$variable};
1537
-	}
1538
-
1539
-	/**
1540
-	 * The hook to setup quick buttons menu.
1541
-	 *
1542
-	 * @param array $profile_areas All the mod buttons.
1543
-	 *
1544
-	 * @api
1545
-	 * @CalledIn SMF 2.1
1546
-	 * @version 1.4.0
1547
-	 * @since 1.4.0
1548
-	 * @uses integrate_prepare_display_context - Hook SMF2.1
1549
-	 * @return void We update the output to add the more action for SFS.
1550
-	 */
1551
-	public static function hook_prepare_display_context(&$output, &$message, $counter): void
1552
-	{
1553
-		global $smcFunc, $scripturl, $context;
1554
-
1555
-		$output['quickbuttons']['more']['sfs'] = array(
1556
-			'label' => $smcFunc['classSFS']->txt('sfs_admin_area'),
1557
-			'href' => $scripturl . '?action=profile;area=sfs;u=' . $output['member']['id'] . ';msg=' . $output['id'],
1558
-			'icon' => 'sfs',
1559
-			'show' => $context['can_moderate_forum']
1560
-		);
1561
-	}
1562
-
1563
-	/**
1564
-	 * We don't do any mod buttons, just use this to inject some CSS.
1565
-	 *
1566
-	 * @param array $mod_buttons All the mod buttons.
1567
-	 *
1568
-	 * @api
1569
-	 * @CalledIn SMF 2.1
1570
-	 * @version 1.4.0
1571
-	 * @since 1.4.0
1572
-	 * @uses integrate_mod_buttons - Hook SMF2.1
1573
-	 * @return void We add some css.
1574
-	 */
1575
-	public static function hook_mod_buttons(&$mod_buttons): void
1576
-	{
1577
-		global $settings;
1578
-
1579
-		addInlineCss('.main_icons.sfs::before { background: url(' . $settings['default_images_url'] . '/admin/sfs.webp) no-repeat; background-size: contain;}');
1580
-	}
1581
-}
1582 0
\ No newline at end of file
... ...
@@ -0,0 +1,1182 @@
1
+<?php
2
+
3
+/**
4
+ * The Main class for Stop Forum Spam
5
+ * @package StopForumSpam
6
+ * @author SleePy <sleepy @ simplemachines (dot) org>
7
+ * @copyright 2023
8
+ * @license 3-Clause BSD https://opensource.org/licenses/BSD-3-Clause
9
+ * @version 1.5.0
10
+ */
11
+class SFS
12
+{
13
+	/**
14
+	 * @var array Our settings information used on saving/changing settings.
15
+	 */
16
+	private array $changedSettings = [];
17
+	private array $extraVerificationOptions = [];
18
+
19
+	/**
20
+	 * @var string Name of the software and its version.  This is so we can branch out from the same base.
21
+	 */
22
+	private string $softwareName = 'smf';
23
+	private string $softwareVersion = '2.1';
24
+
25
+	/**
26
+	 * @var array The block Types.
27
+	 */
28
+	private array $blockTypeMap = [
29
+		'username' => 1,
30
+		'email' => 2,
31
+		'ip' => 3
32
+	];
33
+
34
+	/**
35
+	 * @var string The url we are requesting.
36
+	 */
37
+	private string $requestURL = '';
38
+
39
+	/**
40
+	 * @var array Default settings.
41
+	 */
42
+	private array $defaultSettings = [
43
+		'sfs_enabled' => 1,
44
+		'sfs_expire' => 90,
45
+		'sfs_emailcheck' => 1,
46
+		'sfs_username_confidence' => 50.01,
47
+		'sfs_region' => 0,
48
+		'sfs_verfOptMemPostThreshold' => 5
49
+	];
50
+
51
+	/*
52
+	 * SMF variables we will load into here for easy reference later.
53
+	*/
54
+	private ?string $scripturl;
55
+	private array $context;
56
+	private array $smcFunc;
57
+	/* This is array in "theory" only.  SMF sometimes will null this when pulling from cache and causes an error */
58
+	private ?array $modSettings;
59
+	private ?array $user_info;
60
+	private ?array $txt;
61
+
62
+	/**
63
+	 * Simple setup for the class to be used later correctly.
64
+	 * This simply loads the class into $smcFunc and we can grab this anywhere else later.
65
+	 *
66
+	 * @api
67
+	 * @CalledIn SMF 2.0, SMF 2.1
68
+	 * @version 1.0
69
+	 * @since 1.0
70
+	 * @uses integrate_pre_load - Hook SMF2.0
71
+	 * @uses integrate_pre_load - Hook SMF2.1
72
+	 * @return void No return is generated
73
+	 */
74
+	public static function hook_pre_load(): void
75
+	{
76
+		$GLOBALS['smcFunc']['classSFS'] = self::selfClass();
77
+
78
+		// SMF 2.0 needs some help.
79
+		if ($GLOBALS['smcFunc']['classSFS']->versionCheck('2.0'))
80
+			$GLOBALS['smcFunc']['classSFS']->loadSources([
81
+				'SFS-Admin',
82
+				'SFS-Logs',
83
+				'SFS-Profile'
84
+			]);
85
+	}
86
+
87
+	/**
88
+	 * Creates a self reference to the SFS Log class for use later.
89
+	 *
90
+	 * @version 1.2
91
+	 * @since 1.2
92
+	 * @return object The SFS Log class is returned.
93
+	 */
94
+	public static function selfClass(): self
95
+	{
96
+		if (!isset($GLOBALS['context']['instances'][__CLASS__]))
97
+			$GLOBALS['context']['instances'][__CLASS__] = new self();
98
+
99
+		return $GLOBALS['context']['instances'][__CLASS__];
100
+	}
101
+
102
+	/**
103
+	 * Build the class, figure out what software/version we have.
104
+	 * Loads up the defaults.
105
+	 *
106
+	 * @CalledIn SMF 2.0, SMF 2.1
107
+	 * @version 1.5.0
108
+	 * @since 1.0
109
+	 * @return void No return is generated
110
+	 */
111
+	public function __construct()
112
+	{
113
+		// Is this SMF 2.0?
114
+		if (!function_exists('loadCacheAccelerator'))
115
+			$this->softwareVersion = '2.0';
116
+
117
+		foreach (['scripturl', 'context', 'smcFunc', 'txt', 'modSettings', 'user_info'] as $f)
118
+			$this->{$f} = &$GLOBALS[$f];
119
+
120
+		// Setup the defaults.
121
+		$this->loadDefaults();
122
+	}
123
+
124
+	/**
125
+	 * Handle registration events.
126
+	 *
127
+	 * @param array $regOptions An array from the software with all the registration optins we are going to use to register.
128
+	 * @param array $theme_vars An array from the software with all the possible theme settings we are going to use to register.
129
+	 *
130
+	 * @api
131
+	 * @CalledIn SMF 2.0, SMF 2.1
132
+	 * @CalledAt: action=signup, action=admin;area=regcenter;sa=register
133
+	 * @See SFS::checkRegisterRequest
134
+	 * @version 1.0
135
+	 * @since 1.0
136
+	 * @uses integrate_register - Hook SMF2.1
137
+	 * @uses integrate_register - Hook SMF2.0
138
+	 * @return bool True is success, no other bool is expeicifcly defined yet.
139
+	 */
140
+	public static function hook_register(array &$regOptions, array &$theme_vars): bool
141
+	{
142
+		global $smcFunc;
143
+		return $smcFunc['classSFS']->checkRegisterRequest($regOptions, $theme_vars);
144
+	}
145
+
146
+	/**
147
+	 * Something is attempting to register, we should check them out.
148
+	 *
149
+	 * @param array $regOptions An array from the software with all the registration optins we are going to use to register.
150
+	 * @param array $theme_vars An array from the software with all the possible theme settings we are going to use to register.
151
+	 *
152
+	 * @api
153
+	 * @CalledIn SMF 2.0, SMF 2.1
154
+	 * @CalledAt: action=signup, action=admin;area=regcenter;sa=register
155
+	 * @See SFS::checkRegisterRequest
156
+	 * @version 1.5.0
157
+	 * @since 1.0
158
+	 * @uses integrate_register - Hook SMF2.1
159
+	 * @uses integrate_register - Hook SMF2.0
160
+	 * @return bool True is success, no other bool is expeicifcly defined yet.
161
+	 */
162
+	private function checkRegisterRequest(array &$regOptions, array &$theme_vars): bool
163
+	{
164
+		// Admins are not spammers.. usually.
165
+		if ($regOptions['interface'] == 'admin')
166
+			return true;
167
+
168
+		// Pass everything and let us handle what options we pass on.  We pass the register_vars as these are what we have cleaned up.
169
+		return $this->sfsCheck([
170
+			['username' => $regOptions['register_vars']['member_name']],
171
+			['email' => $regOptions['register_vars']['email_address']],
172
+			['ip' => $regOptions['register_vars']['member_ip']],
173
+			['ip' => $regOptions['register_vars']['member_ip2']],
174
+		], 'register');
175
+	}
176
+
177
+	/**
178
+	 * The caller for a verification test.
179
+	 *
180
+	 * @param array $thisVerification An array from the software with all the verification information we have.
181
+	 * @param array $verification_errors An errors which exist from verification.
182
+	 *
183
+	 * @api
184
+	 * @CalledIn SMF 2.0, SMF 2.1
185
+	 * @See SFS::checkVerificationTest
186
+	 * @version 1.0
187
+	 * @since 1.0
188
+	 * @uses integrate_create_control_verification_test - Hook SMF2.1
189
+	 * @return bool True is success, no other bool is expeicifcly defined yet.
190
+	 */
191
+	public static function hook_create_control_verification_test(array $thisVerification, array &$verification_errors): bool
192
+	{
193
+		global $smcFunc;
194
+		return $smcFunc['classSFS']->checkVerificationTest($thisVerification, $verification_errors);
195
+	}
196
+
197
+	/**
198
+	 * The caller for a verification test.
199
+	 * SMF 2.0 calls this directly as we have no good hook.
200
+	 *
201
+	 * @param array $thisVerification An array from the software with all the verification information we have.
202
+	 * @param array $verification_errors An errors which exist from verification.
203
+	 *
204
+	 * @api
205
+	 * @CalledIn SMF 2.0, SMF 2.1
206
+	 * @version 1.5.0
207
+	 * @since 1.0
208
+	 * @uses create_control_verification - Hook SMF2.0
209
+	 * @uses integrate_create_control_verification_test - Hook SMF2.1
210
+	 * @return bool True is success, no other bool is expeicifcly defined yet.
211
+	 */
212
+	public function checkVerificationTest(array $thisVerification, array &$verification_errors): bool
213
+	{
214
+		// Registration is skipped as we process that differently.
215
+		if ($thisVerification['id'] == 'register')
216
+			return true;
217
+
218
+		// Get our options data.
219
+		$options = $this->getVerificationOptions();
220
+
221
+		// Key => Extended checks.
222
+		$verificationMap = [
223
+			'post' => true,
224
+			'report' => true,
225
+			'search' => $this->user_info['is_guest'] || empty($this->user_info['posts']) || $this->user_info['posts'] < $this->modSettings['sfs_verfOptMemPostThreshold'],
226
+		];
227
+
228
+		foreach (array_filter($verificationMap, function($extendedChecks, $key) use ($thisVerification, $options) {
229
+			return $thisVerification['id'] == $key && in_array($key, $options);
230
+		}, ARRAY_FILTER_USE_BOTH) as $key => $extendedChecks)
231
+			return call_user_func(array($this, 'checkVerificationTest' . ucfirst($key)));
232
+
233
+		// Others areas.  We have to play a guessing game here.
234
+		return $this->checkVerificationTestExtra($thisVerification);
235
+	}
236
+
237
+	/**
238
+	 * Test for a standard post.
239
+	 *
240
+	 * @internal
241
+	 * @CalledIn SMF 2.0, SMF 2.1
242
+	 * @version 1.5.0
243
+	 * @since 1.1
244
+	 * @return bool True is success, no other bool is expeicifcly defined yet.
245
+	 */
246
+	private function checkVerificationTestPost(): bool
247
+	{
248
+		// Guests!
249
+		if ($this->user_info['is_guest'])
250
+		{
251
+			$guestname = !isset($_POST['guestname']) ? '' : trim($_POST['guestname']);
252
+			$email = !isset($_POST['email']) ? '' : trim($_POST['email']);
253
+
254
+			return $this->sfsCheck([
255
+				['username' => $guestname],
256
+				['email' => $email],
257
+				['ip' => $this->user_info['ip']],
258
+				['ip' => $this->user_info['ip2']],
259
+			], 'post');
260
+
261
+		}
262
+		// Members and they don't have enough posts?
263
+		elseif (empty($this->user_info['posts']) || $this->user_info['posts'] < $this->modSettings['sfs_verfOptMemPostThreshold'])
264
+			return $this->sfsCheck([
265
+				['username' => $this->user_info['username']],
266
+				['email' => $this->user_info['email']],
267
+				['ip' => $this->user_info['ip']],
268
+				['ip' => $this->user_info['ip2']],
269
+			], 'post');
270
+		else
271
+			return true;
272
+	}
273
+
274
+	/**
275
+	 * Test for a report.
276
+	 *
277
+	 * @internal
278
+	 * @CalledIn SMF 2.0, SMF 2.1
279
+	 * @version 1.5.0
280
+	 * @since 1.1
281
+	 * @return bool True is success, no other bool is expeicifcly defined yet.
282
+	 */
283
+	private function checkVerificationTestReport(): bool
284
+	{
285
+		$email = !isset($_POST['email']) ? '' : trim($_POST['email']);
286
+
287
+		return $this->sfsCheck([
288
+			['email' => $email],
289
+			['ip' => $this->user_info['ip']],
290
+			['ip' => $this->user_info['ip2']],
291
+		], 'post');
292
+	}
293
+
294
+	/**
295
+	 * Test for a Search.
296
+	 *
297
+	 * @internal
298
+	 * @CalledIn SMF 2.0, SMF 2.1
299
+	 * @version 1.5.0
300
+	 * @since 1.1
301
+	 * @return bool True is success, no other bool is expeicifcly defined yet.
302
+	 */
303
+	private function checkVerificationTestSearch(): bool
304
+	{
305
+		return $this->sfsCheck([
306
+			['ip' => $this->user_info['ip']],
307
+			['ip' => $this->user_info['ip2']],
308
+		], 'search');
309
+	}
310
+
311
+	/**
312
+	 * Test for extras, customizations and other areas that we want to tie in.
313
+	 *
314
+	 * @internal
315
+	 * @CalledIn SMF 2.0, SMF 2.1
316
+	 * @version 1.5.0
317
+	 * @since 1.1
318
+	 * @return bool True is success, no other bool is expeicifcly defined yet.
319
+	 */
320
+	private function checkVerificationTestExtra(array $thisVerification): bool
321
+	{
322
+		foreach (array_filter($this->extraVerificationOptions, function ($option) use ($thisVerification) {return $thisVerification['id'] == $option;})  as $option)
323
+		{
324
+			// Always try to send off IPs.
325
+			$checks = [
326
+				['ip' => $this->user_info['ip']],
327
+				['ip' => $this->user_info['ip2']],
328
+			];
329
+
330
+			// Can we find a username?
331
+			$possibleUserNames = ['username', 'user_name', 'user', 'name', 'realname'];
332
+			$searchKey = current(array_filter($possibleUserNames, function($k) {return !empty($_POST[$k]);}));
333
+			$checks[] = ['username' => $_POST[$searchKey]];
334
+
335
+			// Can we find a email?
336
+			$possibleEmails = ['email', 'emailaddress', 'email_address'];
337
+			$searchKey = current(array_filter($possibleEmails, function($k) {return !empty($_POST[$k]);}));
338
+			$checks[] = ['email' => $_POST[$searchKey]];
339
+
340
+			return $this->sfsCheck($checks, $option);
341
+		}
342
+
343
+		return true;
344
+	}
345
+
346
+	/**
347
+	 * Run checks against the SFS database.
348
+	 *
349
+	 * @param array $checks All the possible checks we would like to preform.
350
+	 * @param string $area The area this is coming from.
351
+	 *
352
+	 * @internal
353
+	 * @CalledIn SMF 2.0, SMF 2.1
354
+	 * @version 1.5.0
355
+	 * @since 1.0
356
+	 * @return bool True is success, no other bool is expeicifcly defined yet.
357
+	 */
358
+	private function sfsCheck(array $checks, string $area = null): bool
359
+	{
360
+		// Send it off.
361
+		$response = $this->SendSFS($checks, $area);
362
+
363
+		// No checks found? Can't do this.
364
+		if ($response === [])
365
+		{
366
+			$this->logAllStats('error', $checks, 'error');
367
+			log_error($this->txt('sfs_request_failure_nodata') . ':' . $requestURL, 'critical');
368
+			return true;
369
+		}
370
+
371
+		$requestBlocked = '';
372
+
373
+		// Are we requiring multiple checks.
374
+		if (!empty($this->modSettings['sfs_required']) && $this->modSettings['sfs_required'] != 'any')
375
+			$requestBlocked = $this->sfsCheckMultiple($response, $area);
376
+		// Otherwise we will check anything enabled and if any match, its found
377
+		else
378
+			$requestBlocked = $this->sfsCheckSingle($response, $area);
379
+
380
+		// Log all the stats?  Debug mode here.
381
+		$this->logAllStats('all', $checks, $requestBlocked);
382
+
383
+		// At this point, we have checked everything, do what needs to be done for our good person.
384
+		if (empty($requestBlocked))
385
+			return true;
386
+
387
+		// You are a bad spammer, don't tell them what was blocked.
388
+		fatal_error($this->txt('sfs_request_blocked'));
389
+	}
390
+
391
+	/**
392
+	 * The caller for a Send check.
393
+	 *
394
+	 * @param array $checks The data we are checking.
395
+	 *
396
+	 * @api
397
+	 * @CalledIn SMF 2.0, SMF 2.1
398
+	 * @version 1.5.0
399
+	 * @since 1.4.0
400
+	 * @return array The results of the check.
401
+	 */
402
+	public function SendSFS(array $checks, string $area = null): array
403
+	{
404
+		$requestURL = $this->buildServerURL();
405
+
406
+		// Lets build our data set, always send it as a bulk.
407
+		$singleCheckFound = $this->buildCheckPath($requestURL, $checks, $area);
408
+
409
+		// No checks found? Can't do this.
410
+		if (empty($singleCheckFound))
411
+			return [];
412
+
413
+		// Send this off.
414
+		return $this->sendSFSCheck($requestURL, $checks, $area);
415
+	}
416
+
417
+	/**
418
+	 * Validate the checks for multiple conditions.
419
+	 *
420
+	 * @param array $response All the resposnes we received.
421
+	 * @param string $area The area this is coming from.
422
+	 *
423
+	 * @internal
424
+	 * @CalledIn SMF 2.0, SMF 2.1
425
+	 * @version 1.5.0
426
+	 * @since 1.5.0
427
+	 * @return string The requests that matched as blocked.
428
+	 */
429
+	private function sfsCheckMultiple(array $response, string $area = null): string
430
+	{
431
+		// When requiring multiple checks, we require all to match.
432
+		$requiredChecks = explode('|', $this->modSettings['sfs_required']);
433
+		$requestBlocked = '';
434
+		$result = true;
435
+		foreach ($requiredChecks as $key)
436
+		{
437
+			$test = call_user_func(array($this, 'sfsCheck_' . $key), $response[$key], $area);
438
+			$requestBlocked .= !empty($test) ? $test . '|' : '';
439
+			$result &= !empty($test);
440
+		}
441
+
442
+		// Not all checks passed, so we will allow it.
443
+		if (!$result)
444
+			return '';	
445
+		return $requestBlocked;		
446
+	}
447
+
448
+	/**
449
+	 * Validate the checks for a single condition.
450
+	 *
451
+	 * @param array $response All the resposnes we received.
452
+	 * @param string $area The area this is coming from.
453
+	 *
454
+	 * @internal
455
+	 * @CalledIn SMF 2.0, SMF 2.1
456
+	 * @version 1.5.0
457
+	 * @since 1.5.0
458
+	 * @return string The request that matched as blocked.
459
+	 */
460
+	private function sfsCheckSingle(array $response, string $area = null): string
461
+	{
462
+		$checkMap = [
463
+			'ip' => !empty($this->modSettings['sfs_ipcheck']) && !empty($response['ip']),
464
+			'username' => !empty($this->modSettings['sfs_usernamecheck']) && !empty($response['username']),
465
+			'email' => !empty($this->modSettings['sfs_emailcheck']) && !empty($response['email'])
466
+		];
467
+
468
+		foreach ($checkMap as $key => $checkEnabled)
469
+			if (empty($requestBlocked) && $checkEnabled)
470
+				$requestBlocked = call_user_func(array($this, 'sfsCheck_' . $key), $response[$key], $area);
471
+
472
+		return $requestBlocked;		
473
+	}
474
+
475
+	/**
476
+	 * Send off the request to SFS and receive a response back
477
+	 *
478
+	 * @param string $requestURL The initial url we will send.
479
+	 * @param array $checks All the possible checks we would like to preform.
480
+	 * @param string $area The area this is coming from.
481
+	 *
482
+	 * @internal
483
+	 * @CalledIn SMF 2.0, SMF 2.1
484
+	 * @version 1.5.0
485
+	 * @since 1.1
486
+	 * @return array data we received back, could be a empty array.
487
+	 */
488
+	private function sendSFSCheck(string $requestURL, array $checks, string $area = null): array
489
+	{
490
+		// SMF 2.0 has the fetch_web_data in the Subs-Packages, 2.1 it is in Subs.php.
491
+		if ($this->versionCheck('2.0', 'smf'))
492
+			$this->loadSources('Subs-Package');
493
+
494
+		// Now we have a URL, lets go get it.
495
+		$result = fetch_web_data($requestURL);
496
+		if ($result === false)
497
+		{
498
+			$this->logAllStats('error', $checks, 'failure');
499
+			log_error($this->txt('sfs_request_failure') . ':' . $requestURL, 'critical');
500
+			return true;
501
+		}
502
+
503
+		$response = $this->decodeJSON($result);
504
+
505
+		// No data received, log it and let them through.
506
+		if (empty($response))
507
+		{
508
+			$this->logAllStats('error', $checks, 'failure');
509
+			log_error($this->txt('sfs_request_failure') . ':' . $requestURL, 'critical');
510
+			return true;
511
+		}
512
+
513
+		return $response;
514
+	}
515
+
516
+	/**
517
+	 * Run checks for IPs
518
+	 *
519
+	 * @param array $ips All the IPs we are checking.
520
+	 * @param string $area If defined the area we are checking.
521
+	 * @internal
522
+	 * @CalledIn SMF 2.0, SMF 2.1
523
+	 * @version 1.5.0
524
+	 * @since 1.1
525
+	 * @return string Request Blocked data if any
526
+	 */
527
+	private function sfsCheck_ip(array $ips, string $area = ''): string
528
+	{
529
+		$this->loadSources(['SFS-Bans']);
530
+
531
+		$requestBlocked = '';
532
+		foreach (array_filter($ips, function ($check) {return !empty($check['appears']);}) as $check)
533
+		{
534
+			// Ban them because they are black listed?
535
+			$autoBlackListResult = '0';
536
+			if (!empty($this->modSettings['sfs_ipcheck_autoban']) && !empty($check['frequency']) && $check['frequency'] == 255)
537
+				$autoBlackListResult = SFSB::AddNewIpBan($check['value']);
538
+
539
+			$this->logBlockedStats('ip', $check);
540
+			$requestBlocked = 'ip,' . $this->smcFunc['htmlspecialchars']($check['value']) . ',' . ($autoBlackListResult ? 1 : 0);
541
+			break;
542
+		}
543
+
544
+		return $requestBlocked;
545
+	}
546
+
547
+	/**
548
+	 * Run checks for Usernames
549
+	 *
550
+	 * @param array $usernames All the usernames we are checking.
551
+	 * @param string $area If defined the area we are checking.
552
+	 * @internal
553
+	 * @CalledIn SMF 2.0, SMF 2.1
554
+	 * @version 1.5.0
555
+	 * @since 1.1
556
+	 * @return string Request Blocked data if any
557
+	 */
558
+	private function sfsCheck_username(array $usernames, string $area = ''): string
559
+	{
560
+		$requestBlocked = '';
561
+		foreach (array_filter($usernames, function ($check) {return !empty($check['appears']);}) as $check)
562
+		{
563
+			// Combine with $area we could also require admin approval above thresholds on things like register.
564
+			$shouldBlock = true;
565
+
566
+			// We are not confident that they should be blocked.
567
+			if (!empty($this->modSettings['sfs_username_confidence']) && !empty($check['confidence']) && $area == 'register' && (float) $this->modSettings['sfs_username_confidence'] > (float) $check['confidence'])
568
+			{
569
+				$this->logAllStats('all', $check, 'username,' . $this->smcFunc['htmlspecialchars']($check['value']) . ',' . $check['confidence']);
570
+				$shouldBlock = false;
571
+			}
572
+
573
+			// Block them.
574
+			if ($shouldBlock)
575
+			{
576
+				$this->logBlockedStats('username', $check);
577
+				$requestBlocked = 'username,' . $this->smcFunc['htmlspecialchars']($check['value']) . ',' . $check['confidence'];
578
+				break;
579
+			}
580
+		}
581
+
582
+		return $requestBlocked;
583
+	}
584
+
585
+	/**
586
+	 * Run checks for Email
587
+	 *
588
+	 * @param array $email All the email we are checking.
589
+	 * @param string $area If defined the area we are checking.
590
+	 * @internal
591
+	 * @CalledIn SMF 2.0, SMF 2.1
592
+	 * @version 1.5.0
593
+	 * @since 1.1
594
+	 * @return string Request Blocked data if any
595
+	 */
596
+	private function sfsCheck_email(array $email, string $area = ''): string
597
+	{
598
+		$requestBlocked = '';
599
+		foreach (array_filter($email, function ($check) {return !empty($check['appears']);}) as $check)
600
+		{
601
+			$this->logBlockedStats('email', $check);
602
+			$requestBlocked = 'email,' . $this->smcFunc['htmlspecialchars']($check['value']);
603
+			break;
604
+		}
605
+
606
+		return $requestBlocked;
607
+	}
608
+
609
+	/**
610
+	 * Run checks against the SFS database.
611
+	 *
612
+	 * @param string $requestURL The initial url we will send.
613
+	 * @param array $checks All the possible checks we would like to preform.
614
+	 * @param string $area The area this is coming from.
615
+	 *
616
+	 * @internal
617
+	 * @CalledIn SMF 2.0, SMF 2.1
618
+	 * @version 1.5.0
619
+	 * @since 1.0
620
+	 * @return bool True we found something to check, false nothing..  $requestURL will be updated with the new data.
621
+	 */
622
+	private function buildCheckPath(string &$requestURL, array $checks, string $area = null): bool
623
+	{
624
+		$singleCheckFound = false;
625
+		foreach ($checks as $chk)
626
+		{
627
+			// Hold up, we are not processing this check.
628
+			$chk = array_filter($chk, function($value, $type) {return !(in_array($type, ['email', 'username', 'ip']) && empty($this->modSettings['sfs_' . $type . 'check']));}, ARRAY_FILTER_USE_BOTH);
629
+
630
+			// No value? Can't do this.
631
+			$chk = array_filter($chk, function($value) {return !empty($value);});
632
+
633
+			foreach ($chk as $type => $value)
634
+			{
635
+				// Emails and usernames must be UTF-8, Only a issue with SMF 2.0.
636
+				if (!$this->context['utf8'] && ($type == 'email' || $type == 'username'))
637
+					$requestURL .= '&' . $type . '[]=' . iconv($this->context['character_set'], 'UTF-8//IGNORE', $value);
638
+				else
639
+					$requestURL .= '&' . $type . '[]=' . urlencode($value);
640
+
641
+				$singleCheckFound = true;
642
+			}
643
+		}
644
+
645
+		return $singleCheckFound;
646
+	}
647
+
648
+	/**
649
+	 * Log that this was blocked.
650
+	 *
651
+	 * @param string $type Either username, email, or ip.  Anything else gets marked uknown.
652
+	 * @param array $check The check data we are logging.
653
+	 *
654
+	 * @internal
655
+	 * @CalledIn SMF 2.0, SMF 2.1
656
+	 * @version 1.5.0
657
+	 * @since 1.0
658
+	 * @return bool True is success, no other bool is expeicifcly defined yet.
659
+	 */
660
+	private function logBlockedStats(string $type, array $check): void
661
+	{
662
+		$this->smcFunc['db_insert']('',
663
+			'{db_prefix}log_sfs',
664
+			[
665
+				'id_type' => 'int',
666
+				'log_time' => 'int',
667
+				'url' => 'string',
668
+				'id_member' => 'int',
669
+				'username' => 'string',
670
+				'email' => 'string',
671
+				'ip' => 'string',
672
+				'ip2' => 'string',
673
+				'checks' => 'string',
674
+				'result' => 'string'
675
+			],
676
+			[
677
+				isset($this->blockTypeMap[$type]) ? $this->blockTypeMap[$type] : 99, // Blocked request
678
+				time(),
679
+				$this->smcFunc['htmlspecialchars']($_SERVER['REQUEST_URL']),
680
+				$this->user_info['id'],
681
+				$type == 'username' ? $check['value'] : '',
682
+				$type == 'email' ? $check['value'] : '',
683
+				$type == 'ip' ? $check['value'] : $this->user_info['ip'],
684
+				$this->user_info['ip2'],
685
+				$this->encodeJSON($check),
686
+				'Blocked'
687
+			],
688
+			['id_sfs', 'id_type']
689
+		);
690
+	}
691
+
692
+	/**
693
+	 * Debug logging that this was blocked..
694
+	 *
695
+	 * @param string $type Either error or all, currently ignored.
696
+	 * @param array $check The check data we are logging.
697
+	 * @param string $DebugMessage Debugging message, sometimes just is error or failure, otherwise a comma separated of what request was blocked.
698
+	 *
699
+	 * @internal
700
+	 * @CalledIn SMF 2.0, SMF 2.1
701
+	 * @version 1.5.0
702
+	 * @since 1.0
703
+	 * @return bool True is success, no other bool is expeicifcly defined yet.
704
+	 */
705
+	private function logAllStats(string $type, array $checks, string $DebugMessage): void
706
+	{
707
+		if ($type == 'all' && empty($this->modSettings['sfs_log_debug']))
708
+			return;
709
+
710
+		$this->smcFunc['db_insert']('',
711
+			'{db_prefix}log_sfs',
712
+			[
713
+				'id_type' => 'int',
714
+				'log_time' => 'int',
715
+				'url' => 'string',
716
+				'id_member' => 'int',
717
+				'username' => 'string',
718
+				'email' => 'string',
719
+				'ip' => 'string',
720
+				'ip2' => 'string',
721
+				'checks' => 'string',
722
+				'result' => 'string'
723
+			],
724
+			[
725
+				0, // Debug type.
726
+				time(),
727
+				$this->smcFunc['htmlspecialchars']($_SERVER['REQUEST_URL']),
728
+				$this->user_info['id'],
729
+				'', // Username
730
+				'', // email
731
+				$this->user_info['ip'],
732
+				$this->user_info['ip2'],
733
+				json_encode($checks),
734
+				$DebugMessage,
735
+			],
736
+			['id_sfs', 'id_type']
737
+		);
738
+	}
739
+
740
+	/**
741
+	 * Decode JSON data and return it.
742
+	 * If we have $smcFunc['json_decode'], we use this as it handles errors natively.
743
+	 * For all others, we simply ensure a proper array is returned in the event of a error.
744
+	 *
745
+	 * @param string $requestData A properly formatted json string.
746
+	 *
747
+	 * @internal
748
+	 * @CalledIn SMF 2.0, SMF 2.1
749
+	 * @version 1.5.0
750
+	 * @since 1.0
751
+	 * @return array The parsed json string is now an array.
752
+	 */
753
+	public function decodeJSON(string $requestData): array
754
+	{
755
+		// Do we have $smcFunc?  It handles errors and logs them as needed.
756
+		if (isset($this->smcFunc['json_decode']) && is_callable($this->smcFunc['json_decode']))
757
+			return $this->smcFunc['json_decode']($requestData, true);
758
+		// Back to the basics.
759
+		else
760
+		{
761
+			$data = @json_decode($requestData, true);
762
+
763
+			// We got a error, return nothing.  Don't log this, not worth it.
764
+			if (json_last_error() !== JSON_ERROR_NONE)
765
+				return [];
766
+			return $data;
767
+		}
768
+	}
769
+
770
+	/**
771
+	 * json JSON data and return it.
772
+	 * If we have $smcFunc['json_encode'], we use this as it handles errors natively.
773
+	 * For all others, we simply ensure a proper array is returned in the event of a error.
774
+	 *
775
+	 * @param array $requestData A properly formatted json string.
776
+	 *
777
+	 * @internal
778
+	 * @CalledIn SMF 2.0, SMF 2.1
779
+	 * @version 1.5.0
780
+	 * @since 1.1
781
+	 * @return string The stringified array.
782
+	 */
783
+	public function encodeJSON(array $requestData): string
784
+	{
785
+		// Do we have $smcFunc?  It handles errors and logs them as needed.
786
+		if (isset($this->smcFunc['json_encode']) && is_callable($this->smcFunc['json_encode']))
787
+			return $this->smcFunc['json_encode']($requestData);
788
+		// Back to the basics.
789
+		else
790
+		{
791
+			$data = @json_encode($requestData);
792
+
793
+			// We got a error, return nothing.  Don't log this, not worth it.
794
+			if (json_last_error() !== JSON_ERROR_NONE)
795
+				return null;
796
+			return $data;
797
+		}
798
+	}
799
+
800
+	/**
801
+	 * Build the SFS Server URL based on our configuration setup.
802
+	 *
803
+	 * @internal
804
+	 * @link: https://www.stopforumspam.com/usage
805
+	 * @CalledIn SMF 2.0, SMF 2.1
806
+	 * @version 1.5.0
807
+	 * @since 1.0
808
+	 * @return array The parsed json string is now an array.
809
+	 */
810
+	private function buildServerURL(): string
811
+	{
812
+		// If we build this once, don't do it again.
813
+		if (!empty($this->requestURL))
814
+			return $this->requestURL;
815
+
816
+		// Get our server info.
817
+		$server = $this->sfsServerMapping()[$this->modSettings['sfs_region']];
818
+
819
+		// Build the base URL, we always use json responses.
820
+		$this->requestURL = 'https://' . $server['host'] . '/api?json';
821
+
822
+		// All the SFS Urls => How we toggle them.
823
+		$sfsMap = [
824
+			'nobadall' => !empty($this->modSettings['sfs_wildcard_email']) && !empty($this->modSettings['sfs_wildcard_username']) && !empty($this->modSettings['sfs_wildcard_ip']),
825
+			'notorexit' => !empty($this->modSettings['sfs_tor_check']) && $this->modSettings['sfs_tor_check'] == 1,
826
+			'badtorexit' => !empty($this->modSettings['sfs_tor_check']) && $this->modSettings['sfs_tor_check'] == 2,
827
+		];
828
+
829
+		// Maybe only certain wildcards are ignored?
830
+		if (empty($sfsMap['nobadall']))
831
+			$sfsMap += [
832
+				'nobadusername' => !empty($this->modSettings['sfs_wildcard_email']),
833
+				'nobademail' => !empty($this->modSettings['sfs_wildcard_username']),
834
+				'nobadip' => !empty($this->modSettings['sfs_wildcard_ip']),
835
+			];
836
+
837
+		// Do we have to filter out from lastseen?
838
+		if (!empty($this->modSettings['sfs_expire']))
839
+			$sfsMap['expire=' . (int) $this->modSettings['sfs_expire']] = true;
840
+
841
+		foreach ($sfsMap as $val => $key)
842
+			if (!empty($key))
843
+				$this->requestURL .= '&' . $val;
844
+
845
+		return $this->requestURL;
846
+	}
847
+
848
+	/**
849
+	 * Setup our possible SFS hosts.
850
+	 *
851
+	 * @internal
852
+	 * @link: https://www.stopforumspam.com/usage
853
+	 * @CalledIn SMF 2.0, SMF 2.1
854
+	 * @version 1.5.0
855
+	 * @since 1.0
856
+	 * @return array The list of servers.
857
+	 */
858
+	public function sfsServerMapping($returnType = null)
859
+	{
860
+		// Global list of servers.
861
+		$serverList = [
862
+			0 => [
863
+				'region' => 'global',
864
+				'label' => $this->txt('sfs_region_global'),
865
+				'host' => 'api.stopforumspam.org',
866
+			],
867
+			1 => [
868
+				'region' => 'us',
869
+				'label' => $this->txt('sfs_region_us'),
870
+				'host' => 'us.stopforumspam.org',
871
+			],
872
+			2 => [
873
+				'region' => 'eu',
874
+				'label' => $this->txt('sfs_region_eu'),
875
+				'host' => 'eruope.stopforumspam.org',
876
+			],
877
+		];
878
+
879
+		// Configs only need the labels.
880
+		if ($returnType == 'config')
881
+			// array_column does not preserve keys, but this is in order already.
882
+			return array_column($serverList, 'label');
883
+
884
+		return $serverList;
885
+	}
886
+
887
+	/**
888
+	 * Our possible verification options.
889
+	 *
890
+	 * @internal
891
+	 * @CalledIn SMF 2.0, SMF 2.1
892
+	 * @version 1.5.0
893
+	 * @since 1.0
894
+	 * @return array The list of servers.
895
+	 */
896
+	private function getVerificationOptions(): array
897
+	{
898
+		$optionsKey = $this->user_info['is_guest'] ? 'sfs_verification_options' : 'sfs_verOptionsMembers';
899
+		$optionsKeyExtra = $this->user_info['is_guest'] ? 'sfs_verification_options_extra' : 'sfs_verOptionsMemExtra';
900
+
901
+		// Standard options.
902
+		$options = $this->Decode($this->modSettings[$optionsKey]);
903
+
904
+		if (empty($options))
905
+			$options = [];
906
+
907
+		// Extras.
908
+		if (!empty($this->modSettings[$optionsKeyExtra]))
909
+		{
910
+			$this->extraVerificationOptions = explode(',', $this->modSettings[$optionsKeyExtra]);
911
+
912
+			if (!empty($this->extraVerificationOptions))
913
+				$options = array_merge($options, $this->extraVerificationOptions);
914
+		}
915
+
916
+		return $options;
917
+	}
918
+
919
+	/**
920
+	 * Our possible default options.
921
+	 * We don't specify them all, just ones that make sense for code development.
922
+	 *
923
+	 * @param bool $undo If true, we reverse any defaults we set.  Makes the admin page work.
924
+	 *
925
+	 * @internal
926
+	 * @CalledIn SMF 2.0, SMF 2.1
927
+	 * @version 1.5.0
928
+	 * @since 1.0
929
+	 * @return void Nothing is returned, we inject into $modSettings.
930
+	 */
931
+	public function loadDefaults(bool $undo = false): bool
932
+	{
933
+		$this->defaultSettings['sfs_verification_options'] = $this->Stringify(['post']);
934
+
935
+		// We undoing this? Maybe a save?
936
+		if ($undo)
937
+		{
938
+			foreach ($this->changedSettings as $key => $value)
939
+				unset($this->modSettings[$key], $this->changedSettings[$key]);
940
+			return true;
941
+		}
942
+
943
+		// Enabled settings.
944
+		foreach ($this->defaultSettings as $key => $value)
945
+			if (!isset($this->modSettings[$key]))
946
+			{
947
+				$this->changedSettings[$key] = null;
948
+				$this->modSettings[$key] = $value;
949
+			}
950
+
951
+		return true;
952
+	}
953
+
954
+	/**
955
+	 * We undo the defaults letting us save the admin page properly.
956
+	 *
957
+	 * @internal
958
+	 * @CalledIn SMF 2.0, SMF 2.1
959
+	 * @version 1.5.0
960
+	 * @since 1.0
961
+	 * @return void Nothing is returned, we inject into $modSettings.
962
+	 */
963
+	public function unloadDefaults(): bool
964
+	{
965
+		return $this->loadDefaults(true);
966
+	}
967
+
968
+	/**
969
+	 * Checks if we are matching an array of versions against a specific version.
970
+	 *
971
+	 * @param string|array $version The version to check, this is converted to an array later on.
972
+	 * @param string $software The software we are matching against.
973
+	 *
974
+	 * @internal
975
+	 * @CalledIn SMF 2.0, SMF 2.1
976
+	 * @version 1.0
977
+	 * @since 1.0
978
+	 * @return bool True if we matched a version, false otherwise.
979
+	 */
980
+	public function versionCheck(array|string $version, string $software = 'smf'): bool
981
+	{
982
+		// We can't do this if the software doesn't match.
983
+		if ($software !== $this->softwareName)
984
+			return false;
985
+
986
+		// Allow multiple versions to pass.
987
+		foreach ((array) $version as $v)
988
+			if ($v == $this->softwareVersion)
989
+				return true;
990
+
991
+		// No match? False.
992
+		return false;
993
+	}
994
+
995
+	/**
996
+	 * A global function for loading our lanague up.
997
+	 * Placeholder to allow easier additional loading or other software/versions to change this as needed.
998
+	 *
999
+	 * @internal
1000
+	 * @CalledIn SMF 2.0, SMF 2.1
1001
+	 * @version 1.5.0
1002
+	 * @since 1.0
1003
+	 * @return void No return is generated here.
1004
+	 */
1005
+	public function loadLanguage(array|string $languages = 'StopForumSpam'): string
1006
+	{
1007
+		return loadLanguage(implode('+', (array) $languages));
1008
+	}
1009
+
1010
+	/**
1011
+	 * A global function for loading $txt strings.
1012
+	 *
1013
+	 * @param string $key The key of the text string we want to load.
1014
+	 * @internal
1015
+	 * @CalledIn SMF 2.0, SMF 2.1
1016
+	 * @version 1.1
1017
+	 * @since 1.1
1018
+	 * @return string The text string.
1019
+	 */
1020
+	public function txt($key): string
1021
+	{
1022
+		// Load the language if its not here already.
1023
+		if (!isset($this->txt[$key]))
1024
+			$this->loadLanguage();
1025
+
1026
+		if (!isset($this->txt[$key]))
1027
+			return '';
1028
+
1029
+		return $this->txt[$key];
1030
+	}
1031
+
1032
+	/*
1033
+	 * Wrapper for our encoding function.
1034
+	 *
1035
+	 * @api
1036
+	 * @version 1.5.0
1037
+	 * @since 1.5.0
1038
+	*/
1039
+	private function Stringify($data): string
1040
+	{
1041
+		$encodeFunc = 'json_encode';
1042
+		if ($this->versionCheck('2.0', 'smf'))
1043
+			$encodeFunc = 'serialize';
1044
+
1045
+		return $encodeFunc($data);
1046
+	}
1047
+
1048
+	/*
1049
+	 * Wrapper for our decoding function.
1050
+	 *
1051
+	 * @api
1052
+	 * @version 1.5.0
1053
+	 * @since 1.5.0
1054
+	*/
1055
+	private function Decode(string $data)
1056
+	{
1057
+		if ($this->versionCheck('2.0', 'smf') && !empty($data))
1058
+			return safe_unserialize($data);
1059
+		elseif (!empty($data))
1060
+			return $this->decodeJSON($data);
1061
+	}
1062
+
1063
+	/*
1064
+	 * Wrapper for validateToken.
1065
+	 *
1066
+	 * @api
1067
+	 * @version 1.5.0
1068
+	 * @since 1.5.0
1069
+	 * @param array $sourcesThe list of additional sources to load.
1070
+	*/
1071
+	public function createToken($action, $type = 'post'): array|null
1072
+	{
1073
+		if (!$this->versionCheck('2.0', 'smf'))
1074
+			return createToken($action, $type);
1075
+		return null;
1076
+	}
1077
+
1078
+	/*
1079
+	 * Wrapper for createToken.
1080
+	 *
1081
+	 * @api
1082
+	 * @version 1.5.0
1083
+	 * @since 1.5.0
1084
+	 * @param array $sourcesThe list of additional sources to load.
1085
+	*/
1086
+	public function validateToken($action, $type = 'post', $reset = true): bool
1087
+	{
1088
+		if (!$this->versionCheck('2.0', 'smf'))
1089
+			return validateToken($action, $type, $reset);
1090
+		return true;
1091
+	}
1092
+
1093
+	/*
1094
+	 * Load additional sources files.
1095
+	 *
1096
+	 * @version 1.5.0
1097
+	 * @since 1.5.0
1098
+	 * @param array $sourcesThe list of additional sources to load.
1099
+	*/
1100
+	public function loadSources(array|string $sources): void
1101
+	{
1102
+		array_map(function($rs) {
1103
+			require_once($GLOBALS['sourcedir'] . DIRECTORY_SEPARATOR . strtr($rs, ['SFS' => 'StopForumSpam' . DIRECTORY_SEPARATOR . 'SFS']) . '.php');
1104
+		}, (array) $sources);
1105
+	}
1106
+
1107
+	/*
1108
+	 * Load additional template files.
1109
+	 * There are 2 way to pass multiple languages in.  A single string or an array.
1110
+	 *
1111
+	 * @version 1.5.0
1112
+	 * @since 1.5.0
1113
+	 * @calls: $sourcedir/Load.php:loadTemplate
1114
+	 * @param $languages array|string The list of languages to load.
1115
+	*/
1116
+	public function loadTemplate(array|string $templates): void
1117
+	{
1118
+		array_map(function($t) {
1119
+			loadTemplate($t);
1120
+		}, (array) $templates);
1121
+	}
1122
+
1123
+	/**
1124
+	 * Get some data
1125
+	 *
1126
+	 * @param string $variable The data we are looking for..
1127
+	 *
1128
+	 * @internal
1129
+	 * @CalledIn SMF 2.0, SMF 2.1
1130
+	 * @version 1.2
1131
+	 * @since 1.2
1132
+	 * @return bool True upon success, false otherwise.
1133
+	 */
1134
+	public function get(string $variable)
1135
+	{
1136
+		if (in_array($variable, ['softwareName', 'softwareVersion']))
1137
+			return $this->{$variable};
1138
+	}
1139
+
1140
+	/**
1141
+	 * The hook to setup quick buttons menu.
1142
+	 *
1143
+	 * @param array $profile_areas All the mod buttons.
1144
+	 *
1145
+	 * @api
1146
+	 * @CalledIn SMF 2.1
1147
+	 * @version 1.5.0
1148
+	 * @since 1.4.0
1149
+	 * @uses integrate_prepare_display_context - Hook SMF2.1
1150
+	 * @return void We update the output to add the more action for SFS.
1151
+	 */
1152
+	public static function hook_prepare_display_context(&$output, &$message, $counter): void
1153
+	{
1154
+		global $smcFunc, $scripturl, $context;
1155
+
1156
+		$output['quickbuttons']['more']['sfs'] = [
1157
+			'label' => $smcFunc['classSFS']->txt('sfs_admin_area'),
1158
+			'href' => $scripturl . '?action=profile;area=sfs;u=' . $output['member']['id'] . ';msg=' . $output['id'],
1159
+			'icon' => 'sfs',
1160
+			'show' => $context['can_moderate_forum']
1161
+		];
1162
+	}
1163
+
1164
+	/**
1165
+	 * We don't do any mod buttons, just use this to inject some CSS.
1166
+	 *
1167
+	 * @param array $mod_buttons All the mod buttons.
1168
+	 *
1169
+	 * @api
1170
+	 * @CalledIn SMF 2.1
1171
+	 * @version 1.4.0
1172
+	 * @since 1.4.0
1173
+	 * @uses integrate_mod_buttons - Hook SMF2.1
1174
+	 * @return void We add some css.
1175
+	 */
1176
+	public static function hook_mod_buttons(&$mod_buttons): void
1177
+	{
1178
+		global $settings;
1179
+
1180
+		addInlineCss('.main_icons.sfs::before { background: url(' . $settings['default_images_url'] . '/admin/sfs.webp) no-repeat; background-size: contain;}');
1181
+	}
1182
+}
0 1183
\ No newline at end of file
... ...
@@ -24,53 +24,16 @@ function template_profile_tracksfs()
24 24
 			<table class="table_grid">
25 25
 				<thead>
26 26
 					<tr class="title_bar">
27
-						<th class="lefttext half_table">', $txt['sfs_check'], '</th>
28
-						<th class="lefttext half_table">', $txt['sfs_result'], '</th>
27
+						<th class="lefttext">', $txt['sfs_check'], '</th>
28
+						<th class="lefttext">', $txt['sfs_value'], '</th>
29
+						<th class="lefttext">', $txt['sfs_result'], '</th>
29 30
 					</tr>
30 31
 				</thead>
31 32
 				<tbody>';
32 33
 
33 34
 	foreach ($context['sfs_checks'] as $id_check => $checkGrp)
34
-	{
35 35
 		foreach ($checkGrp as $check)
36
-		{
37
-			echo '
38
-					<tr class="windowbg">
39
-						<td title="sfs_check_', $id_check, '">
40
-							', $txt['sfs_check_' . $id_check];
41
-
42
-			// Show the IP we tested.
43
-			if ($id_check === 'ip')
44
-				echo ' <span class="smalltext">(', $check['value'], ')</span>';
45
-
46
-			echo '
47
-						</td>
48
-						<td class="smalltext">
49
-							', (!empty($check['appears']) ? $txt['yes'] : $txt['no']);
50
-
51
-			// Some checks will show the last seen, convert it and show it.
52
-			if (!empty($check['lastseen']))
53
-				echo '<br>' . $txt['sfs_last_seen'] . ': ' . timeformat(strtotime($check['lastseen']));
54
-
55
-			if (!empty($check['confidence']))
56
-				echo '<br>' . $txt['sfs_confidence'] . ': ' . $check['confidence'];
57
-
58
-			if (!empty($check['frequency']))
59
-				echo '<br>' . $txt['sfs_frequency'] . ': ' . $check['frequency'];
60
-
61
-			// IP address may be normalized
62
-			if (!empty($check['torexit']))
63
-				echo '<br>' . $txt['sfs_torexit'];
64
-
65
-			// IP address may be normalized
66
-			if (!empty($check['asn']))
67
-				echo '<br><a href="', $scripturl, '?action=profile;area=tracking;sa=ip;searchip=' . urlencode(str_replace('::*', ':*', !empty($check['normalized']) ? $check['normalized'] . '*' : $check['value'])) . '">', $txt['trackIP'], ' ',  (!empty($check['normalized']) ? $check['normalized'] . '*' : $check['value']), '</a>';
68
-
69
-			echo '
70
-						</td>
71
-					</tr>';
72
-		}
73
-	}
36
+			template_sfsa_result_row($id_check, $check, true);
74 37
 
75 38
 	echo '
76 39
 				</tbody>
... ...
@@ -112,7 +75,7 @@ function template_profile_tracksfs()
112 75
 
113 76
 function template_sfsa_testapi()
114 77
 {
115
-	global $context, $txt, $scripturl;
78
+	global $context, $txt;
116 79
 
117 80
 	echo '
118 81
 		<div id="admin_form_wrapper">
... ...
@@ -159,6 +122,13 @@ function template_sfsa_testapi()
159 122
 	if (empty($context['test_sent']))
160 123
 		return;
161 124
 
125
+	template_sfsa_testapi_results();
126
+}
127
+
128
+function template_sfsa_testapi_results()
129
+{
130
+	global $context, $txt;
131
+
162 132
 	echo '
163 133
 		<div class="tborder">
164 134
 			<div class="cat_bar">
... ...
@@ -176,13 +146,28 @@ function template_sfsa_testapi()
176 146
 				<tbody>';
177 147
 
178 148
 	foreach ($context['sfs_checks'] as $id_check => $checkGrp)
179
-	{
180 149
 		foreach ($checkGrp as $check)
150
+			template_sfsa_result_row($id_check, $check);
151
+
152
+	echo '
153
+				</tbody>
154
+			</table>';
155
+}
156
+
157
+function template_sfsa_result_row(string $id_check, array $check, bool $show_ip = false)
181 158
 {
159
+	global $context, $txt, $scripturl;
160
+
182 161
 	echo '
183 162
 					<tr class="windowbg">
184 163
 						<td title="sfs_check_', $id_check, '">
185
-							', $txt['sfs_check_' . $id_check], '
164
+							', $txt['sfs_check_' . $id_check];
165
+
166
+			// Show the IP we tested.
167
+			if ($show_ip && $id_check === 'ip')
168
+				echo ' <span class="smalltext">(', $check['value'], ')</span>';
169
+
170
+			echo '
186 171
 						</td>
187 172
 						<td>
188 173
 							', $check['value'], '
... ...
@@ -212,10 +197,3 @@ function template_sfsa_testapi()
212 197
 						</td>
213 198
 					</tr>';
214 199
 }
215 200
\ No newline at end of file
216
-	}
217
-
218
-	echo '
219
-				</tbody>
220
-			</table>';
221
-
222
-}
223 201
\ No newline at end of file
... ...
@@ -0,0 +1,511 @@
1
+<?php
2
+
3
+/**
4
+ * The Admin class for Stop Forum Spam
5
+ * @package StopForumSpam
6
+ * @author SleePy <sleepy @ simplemachines (dot) org>
7
+ * @copyright 2023
8
+ * @license 3-Clause BSD https://opensource.org/licenses/BSD-3-Clause
9
+ * @version 1.5.0
10
+ */
11
+class SFSA
12
+{
13
+	private $SFSclass = null;
14
+
15
+	/**
16
+	 * @var string URLS we need to SFS for UI presentation.
17
+	 */
18
+	private string $urlSFSipCheck = 'https://www.stopforumspam.com/ipcheck/%1$s';
19
+	private string $urlSFSsearch = 'https://www.stopforumspam.com/search/%1$s';
20
+
21
+	/**
22
+	 * @var string The URL for the admin page.
23
+	 */
24
+	private ?string $adminPageURL = null;
25
+	private ?string $adminLogURL = null;
26
+	private ?string $adminTestURL = null;
27
+
28
+	/*
29
+	 * SMF variables we will load into here for easy reference later.
30
+	*/
31
+	private string $scripturl;
32
+	private array $context;
33
+	private array $smcFunc;
34
+	/* This is array in "theory" only.  SMF sometimes will null this when pulling from cache and causes an error */
35
+	private ?array $modSettings;
36
+	private ?array $user_info;
37
+	private ?array $txt;
38
+
39
+	/**
40
+	 * Creates a self reference to the ASL class for use later.
41
+	 *
42
+	 * @version 1.0
43
+	 * @since 1.0
44
+	 * @return object The SFS Admin class is returned.
45
+	 */
46
+	public static function selfClass(): self
47
+	{
48
+		if (!isset($GLOBALS['context']['instances'][__CLASS__]))
49
+			$GLOBALS['context']['instances'][__CLASS__] = new self();
50
+
51
+		return $GLOBALS['context']['instances'][__CLASS__];
52
+	}
53
+
54
+	/**
55
+	 * Build the class, figure out what software/version we have.
56
+	 * Loads up the defaults.
57
+	 *
58
+	 * @CalledIn SMF 2.0, SMF 2.1
59
+	 * @version 1.5.0
60
+	 * @since 1.0
61
+	 * @return void No return is generated
62
+	 */
63
+	public function __construct()
64
+	{
65
+		$this->scripturl = $GLOBALS['scripturl'];
66
+		foreach (['context', 'smcFunc', 'txt', 'modSettings', 'user_info'] as $f)
67
+			$this->{$f} = &$GLOBALS[$f];
68
+
69
+		$this->SFSclass = &$this->smcFunc['classSFS'];			
70
+	}
71
+
72
+	/**
73
+	 * Creates the hook to the class for the admin areas.
74
+	 *
75
+	 * @param array $admin_areas A associate array from the software with all valid admin areas.
76
+	 *
77
+	 * @api
78
+	 * @CalledIn SMF 2.0, SMF 2.1
79
+	 * @see SFSA::setupAdminAreas()
80
+	 * @version 1.0
81
+	 * @since 1.0
82
+	 * @uses integrate__admin_areas - Hook SMF2.0
83
+	 * @uses integrate__admin_areas - Hook SMF2.1
84
+	 * @return void No return is generated
85
+	 */
86
+	public static function hook_admin_areas(array &$admin_areas)
87
+	{
88
+		return self::selfClass()->setupAdminAreas($admin_areas);
89
+	}
90
+
91
+	/**
92
+	 * Startup the Admin Panels Additions.
93
+	 * Where things appear are based on what software/version you have.
94
+	 *
95
+	 * @param array $admin_areas A associate array from the software with all valid admin areas.
96
+	 *
97
+	 * @internal
98
+	 * @CalledIn SMF 2.0, SMF 2.1
99
+	 * @version 1.5.0
100
+	 * @since 1.0
101
+	 * @uses integrate__admin_areas - Hook SMF2.0
102
+	 * @uses integrate__admin_areas - Hook SMF2.1
103
+	 * @return void No return is generated
104
+	 */
105
+	private function setupAdminAreas(array &$admin_areas): void
106
+	{
107
+		// The main config is the same.
108
+		$this->adminPageURL = $this->scripturl . '?action=admin;area=modsettings;sa=sfs';
109
+		$admin_areas['config']['areas']['modsettings']['subsections']['sfs'] = [
110
+			$this->SFSclass->txt('sfs_admin_area')
111
+		];
112
+
113
+		// Add the menu item.
114
+		if ($this->SFSclass->versionCheck('2.0', 'smf'))
115
+		{
116
+			$this->adminLogURL = $this->scripturl . '?action=admin;area=modsettings;sa=sfslog';
117
+			$this->adminTestURL = $this->scripturl . '?action=admin;area=modsettings;sa=sfstest';
118
+
119
+			$admin_areas['config']['areas']['modsettings']['subsections']['sfslog'] = [
120
+				$this->SFSclass->txt('sfs_admin_logs')
121
+			];
122
+			$admin_areas['config']['areas']['modsettings']['subsections']['sfstest'] = [
123
+				$this->SFSclass->txt('sfs_admin_test')
124
+			];
125
+		}
126
+		else
127
+		{
128
+			$this->adminLogURL = $this->scripturl . '?action=admin;area=logs;sa=sfslog';
129
+			$this->adminTestURL = $this->scripturl . '?action=admin;area=regcenter;sa=sfstest';
130
+
131
+			$admin_areas['maintenance']['areas']['logs']['subsections']['sfslog'] = [
132
+				$this->SFSclass->txt('sfs_admin_logs'),
133
+			];
134
+			$admin_areas['members']['areas']['regcenter']['subsections']['sfstest'] = [
135
+				$this->SFSclass->txt('sfs_admin_test')
136
+			];
137
+		}
138
+	}
139
+
140
+	/**
141
+	 * Setup the Modification's setup page.
142
+	 * For some versions, we put the logs into the modifications sections, its easier.
143
+	 *
144
+	 * @param array $subActions A associate array from the software with all valid modification sections.
145
+	 *
146
+	 * @api
147
+	 * @CalledIn SMF 2.0, SMF 2.1
148
+	 * @see SFSA::setupModifyModifications()
149
+	 * @version 1.0
150
+	 * @since 1.0
151
+	 * @uses integrate_modify_modifications - Hook SMF2.0
152
+	 * @uses integrate_modify_modifications - Hook SMF2.1
153
+	 * @return void No return is generated
154
+	 */
155
+	public static function hook_modify_modifications(array &$subActions)
156
+	{
157
+		return self::selfClass()->setupModifyModifications($subActions);
158
+	}
159
+
160
+	/**
161
+	 * Setup the Modifications section links.
162
+	 * For some versions we add the logs here as well.
163
+	 *
164
+	 * @param array $subActions A associate array from the software with all valid modification sections.
165
+	 *
166
+	 * @internal
167
+	 * @CalledIn SMF 2.0, SMF 2.1
168
+	 * @version 1.4.0
169
+	 * @since 1.0
170
+	 * @uses integrate_modify_modifications - Hook SMF2.0
171
+	 * @uses integrate_modify_modifications - Hook SMF2.1
172
+	 * @return void No return is generated
173
+	 */
174
+	private function setupModifyModifications(array &$subActions): void
175
+	{
176
+		$subActions['sfs'] = 'SFSA::startupAdminConfiguration';
177
+
178
+		// Only in SMF 2.0 do we drop logs here.
179
+		if ($this->SFSclass->versionCheck('2.0', 'smf'))
180
+		{
181
+			$this->SFSclass->loadSources(['SFS-Logs']);
182
+			$subActions['sfslog'] = 'SFSL::startupLogs';
183
+			$subActions['sfstest'] = 'SFSA::startupTest';
184
+		}
185
+	}
186
+
187
+	/**
188
+	 * The configuration caller.
189
+	 *
190
+	 * @param bool $return_config If true, returns the configuration options for searches.
191
+	 *
192
+	 * @api
193
+	 * @CalledIn SMF 2.0, SMF 2.1
194
+	 * @see SFSA::setupSFSConfiguration
195
+	 * @version 1.0
196
+	 * @since 1.0
197
+	 * @uses integrate_modify_modifications - Hook SMF2.0
198
+	 * @uses integrate_modify_modifications - Hook SMF2.1
199
+	 * @return void No return is generated
200
+	 */
201
+	public static function startupAdminConfiguration(bool $return_config = false)
202
+	{
203
+		return self::selfClass()->setupSFSConfiguration($return_config);
204
+	}
205
+
206
+	/**
207
+	 * The actual settings page.
208
+	 *
209
+	 * @param bool $return_config If true, returns the configuration options for searches.
210
+	 *
211
+	 * @internal
212
+	 * @CalledIn SMF 2.0, SMF 2.1
213
+	 * @version 1.5.0
214
+	 * @since 1.0
215
+	 * @uses integrate_modify_modifications - Hook SMF2.0
216
+	 * @uses integrate_modify_modifications - Hook SMF2.1
217
+	 * @return void No return is generated
218
+	 */
219
+	private function setupSFSConfiguration(bool $return_config = false): array
220
+	{
221
+		$config_vars = $this->getConfiguraiton();
222
+
223
+		if ($return_config)
224
+			return $config_vars;
225
+
226
+		// Saving?
227
+		if (isset($_GET['save']))
228
+			$this->saveConfiguraiton($config_vars);
229
+
230
+		$this->context['post_url'] = $this->adminPageURL . ';save';
231
+		prepareDBSettingContext($config_vars);
232
+
233
+		return [];
234
+	}
235
+
236
+	/**
237
+	 * Get all the valid settings.
238
+	 *
239
+	 * @param array $config_vars The configuration variables..
240
+	 *
241
+	 * @internal
242
+	 * @CalledIn SMF 2.0, SMF 2.1
243
+	 * @version 1.5.0
244
+	 * @since 1.5.0
245
+	 * @return void No return is generated
246
+	 */
247
+	private function getConfiguraiton(): array
248
+	{
249
+		return [
250
+				['title', 'sfsgentitle', 'label' => $this->SFSclass->txt('sfs_general_title')],
251
+
252
+				['check', 'sfs_enabled'],
253
+				['int', 'sfs_expire'],
254
+			'',
255
+				['select', 'sfs_required', [
256
+					'any' => $this->SFSclass->txt('sfs_required_any'),
257
+					'email|ip' => $this->SFSclass->txt('sfs_required_email_ip'),
258
+					'email|username' => $this->SFSclass->txt('sfs_required_email_username'),
259
+					'username|ip' => $this->SFSclass->txt('sfs_required_username_ip'),
260
+				]],
261
+			'',
262
+				['check', 'sfs_emailcheck'],
263
+				['check', 'sfs_usernamecheck'],
264
+				['float', 'sfs_username_confidence', 'step' => '0.01'],
265
+				['check', 'sfs_ipcheck'],
266
+				['check', 'sfs_ipcheck_autoban'],
267
+			'',
268
+				['select', 'sfs_region', $this->SFSclass->sfsServerMapping('config')],
269
+			'',
270
+				['check', 'sfs_wildcard_email'],
271
+				['check', 'sfs_wildcard_username'],
272
+				['check', 'sfs_wildcard_ip'],
273
+			'',
274
+				['select', 'sfs_tor_check', [
275
+					0 => $this->SFSclass->txt('sfs_tor_check_block'),
276
+					1 => $this->SFSclass->txt('sfs_tor_check_ignore'),
277
+					2 => $this->SFSclass->txt('sfs_tor_check_bad'),
278
+				]],
279
+			'',
280
+				['check', 'sfs_enablesubmission'],
281
+				['text', 'sfs_apikey'],
282
+			'',
283
+				['title', 'sfsverftitle', 'label' => $this->SFSclass->txt('sfs_verification_title')],
284
+				['desc', 'sfsverfdesc', 'label' => $this->SFSclass->txt('sfs_verification_desc')],
285
+				['select', 'sfs_verification_options', [
286
+					'post' => $this->SFSclass->txt('sfs_verification_options_post'),
287
+					'report' => $this->SFSclass->txt('sfs_verification_options_report'),
288
+					'search' => $this->SFSclass->txt('sfs_verification_options_search'),
289
+				], 'multiple' => true],			
290
+				['text', 'sfs_verification_options_extra', 'subtext' => $this->SFSclass->txt('sfs_verification_options_extra_subtext')],
291
+
292
+			'',
293
+				['select', 'sfs_verOptionsMembers', [
294
+					'post' => $this->SFSclass->txt('sfs_verification_options_post'),
295
+					'search' => $this->SFSclass->txt('sfs_verification_options_search'),
296
+				], 'multiple' => true],
297
+				['text', 'sfs_verOptionsMemExtra', 'subtext' => $this->SFSclass->txt('sfs_verification_options_extra_subtext')],
298
+				['int', 'sfs_verfOptMemPostThreshold'],
299
+			'',
300
+				['check', 'sfs_log_debug'],
301
+		];
302
+	}
303
+
304
+	/**
305
+	 * Save the actual settings.
306
+	 *
307
+	 * @param array $config_vars The configuration variables..
308
+	 *
309
+	 * @internal
310
+	 * @CalledIn SMF 2.0, SMF 2.1
311
+	 * @version 1.5.0
312
+	 * @since 1.5.0
313
+	 * @return void No return is generated
314
+	 */
315
+	private function saveConfiguraiton(array $config_vars): void
316
+	{
317
+		// Turn the defaults off.
318
+		$this->SFSclass->unloadDefaults();
319
+		checkSession();
320
+
321
+		// If we are automatically banning IPs, make sure we have a ban group.
322
+		if (isset($_POST['sfs_ipcheck_autoban']) && empty($this->modSettings['sfs_ipcheck_autoban_group']))
323
+		{
324
+			$this->SFSclass->loadSources('SFS-Bans');
325
+			SFSB::AdminCreateBanGroup(true);
326
+		}
327
+
328
+		saveDBSettings($config_vars);
329
+
330
+		writeLog();
331
+		redirectexit($this->adminPageURL);
332
+	}
333
+
334
+	/**
335
+	 * In some software/versions, we can hook into the members registration center section.
336
+	 * In others we hook into the modifications settings.
337
+	 *
338
+	 * @param array $subActions All possible sub actions.
339
+	 *
340
+	 * @api
341
+	 * @CalledIn SMF 2.1
342
+	 * @See SFSA::startupTest
343
+	 * @version 1.5.0
344
+	 * @since 1.4.0
345
+	 * @uses integrate_manage_registrations - Hook SMF2.1
346
+	 * @return void No return is generated
347
+	 */
348
+	public static function hook_manage_registrations(array &$subActions): bool
349
+	{
350
+		global $context;
351
+
352
+		// Add our logs sub action.
353
+		$subActions['sfstest'] = ['SFSA::startupTest', 'admin_forum'];
354
+
355
+		if (isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'sfstest' && allowedTo('admin_forum'))
356
+			$context['sub_action'] = 'sfstest';
357
+
358
+		return self::selfClass()->AddToRegCenterMenu($subActions);
359
+	}
360
+
361
+	/**
362
+	 * Add the SFS Test to the regcenter menu.
363
+	 *
364
+	 * @param array $log_functions All possible log functions.
365
+	 *
366
+	 * @CalledIn SMF 2.1
367
+	 * @See SFSA::startupTest
368
+	 * @version 1.5.0
369
+	 * @since 1.4.0
370
+	 * @return void No return is generated
371
+	 */
372
+	public function AddToRegCenterMenu(array &$subActions): bool
373
+	{
374
+		$this->context[$this->context['admin_menu_name']]['tab_data']['tabs']['sfstest'] = [
375
+			'description' => $this->SFSclass->txt('sfs_admin_test_desc'),
376
+		];
377
+
378
+		return true;
379
+	}
380
+
381
+	/**
382
+	 * Test API startup caller.
383
+	 * This has a $return_config just for simply complying with properly for searching the admin panel.
384
+	 *
385
+	 * @param bool $return_config If true, returns empty array to prevent breaking old SMF installs.
386
+	 *
387
+	 * @api
388
+	 * @CalledIn SMF 2.1
389
+	 * @See SFSA::loadTestAPI
390
+	 * @version 1.4.0
391
+	 * @since 1.4.0
392
+	 * @uses hook_manage_registrations - Hook SMF2.1
393
+	 * @uses setupModifyModifications - Injected SMF2.0
394
+	 * @return void No return is generated
395
+	 */
396
+	public static function startupTest(bool $return_config = false): array
397
+	{
398
+		return self::selfClass()->loadTestAPI();
399
+	}
400
+
401
+	/**
402
+	 * Actually do the test API.
403
+	 * This has a $return_config just for simply complying with properly for searching the admin panel.
404
+	 *
405
+	 * @param bool $return_config If true, returns empty array to prevent breaking old SMF installs.
406
+	 *
407
+	 * @api
408
+	 * @CalledIn SMF2.0, SMF 2.1
409
+	 * @version 1.5.0
410
+	 * @since 1.4.0
411
+	 * @uses hook_manage_registrations - Hook SMF2.1
412
+	 * @uses setupModifyModifications - Injected SMF2.0
413
+	 * @return void No return is generated
414
+	 */
415
+	public function loadTestAPI(bool $return_config = false): array
416
+	{
417
+		// No Configs.
418
+		if ($return_config)
419
+			return [];
420
+
421
+		$this->context['token_check'] = 'sfs_testapi';
422
+		$this->SFSclass->loadLanguage();
423
+
424
+		// The reuslts output.
425
+		$this->context['test_sent'] = isset($_POST['send']);
426
+		$this->context['sfs_checks'] = $this->loadTestApiChecks();
427
+
428
+		// Sending the data?
429
+		if ($this->context['test_sent'])
430
+			$this->peformTestApiCheck();
431
+
432
+		// Load our template.
433
+		$this->SFSclass->loadTemplate('StopForumSpam');
434
+		$this->context['sub_template'] = 'sfsa_testapi';
435
+
436
+		$this->context['sfs_test_url'] = $this->adminTestURL;
437
+		if (!$this->SFSclass->createToken($this->context['token_check'], 'post'))
438
+			unset($this->context['token_check']);
439
+
440
+		return [];
441
+	}
442
+
443
+	/**
444
+	 * Load the Test API Checks.
445
+	 *
446
+	 * @api
447
+	 * @CalledIn SMF2.0, SMF 2.1
448
+	 * @version 1.5.0
449
+	 * @since 1.5.0
450
+	 * @return array SFS Checks
451
+	 */
452
+	private function loadTestApiChecks(): array
453
+	{
454
+		return [
455
+			'username' => [
456
+				0 => [
457
+					'enabled' => !empty($this->modSettings['sfs_usernamecheck']),
458
+					'value' => !empty($_POST['username']) ? $this->smcFunc['htmlspecialchars']($_POST['username']) : $this->user_info['name'],
459
+					'results' => null
460
+				],
461
+			],
462
+			'email' => [
463
+				0 => [
464
+					'enabled' => !empty($this->modSettings['sfs_emailcheck']),
465
+					'value' => !empty($_POST['email']) ? $this->smcFunc['htmlspecialchars']($_POST['email']) : $this->user_info['email'],
466
+					'results' => null
467
+				],
468
+			],
469
+			'ip' => [
470
+				0 => [
471
+					'enabled' => !empty($this->modSettings['sfs_ipcheck']),
472
+					'value' => !empty($_POST['ip']) ? $this->smcFunc['htmlspecialchars']($_POST['ip']) : $this->user_info['ip'],
473
+					'results' => null
474
+				],
475
+			],
476
+		];
477
+	}
478
+
479
+	/**
480
+	 * Load the Test API Checks.
481
+	 *
482
+	 * @api
483
+	 * @CalledIn SMF2.0, SMF 2.1
484
+	 * @version 1.5.0
485
+	 * @since 1.5.0
486
+	 * @return array SFS Checks
487
+	 */
488
+	private function peformTestApiCheck(): void
489
+	{
490
+		checkSession();
491
+		$this->SFSclass->validateToken($this->context['token_check'], 'post');
492
+
493
+		$username = $this->smcFunc['htmlspecialchars']($_POST['username']);
494
+		$email = $this->smcFunc['htmlspecialchars']($_POST['email']);
495
+		$ip = $this->smcFunc['htmlspecialchars']($_POST['ip']);
496
+			
497
+		$response = $this->SFSclass->SendSFS([
498
+				['username' => $username],
499
+				['email' => $email],
500
+				['ip' => $ip],
501
+		], 'test');
502
+
503
+		// No checks found? Can't do this.
504
+		if (empty($response) || !is_array($response) || empty($response['success']))
505
+			$this->context['test_api_error'] = $this->SFSclass->txt('sfs_request_failure_nodata');
506
+		else
507
+			// Parse all the responses out.
508
+			foreach($this->context['sfs_checks'] as $key => &$res)
509
+				$res[0] += $response[$key][0];
510
+	}
511
+}
0 512
\ No newline at end of file
... ...
@@ -0,0 +1,400 @@
1
+<?php
2
+
3
+/**
4
+ * The Ban class for Stop Forum Spam
5
+ * @package StopForumSpam
6
+ * @author SleePy <sleepy @ simplemachines (dot) org>
7
+ * @copyright 2023
8
+ * @license 3-Clause BSD https://opensource.org/licenses/BSD-3-Clause
9
+ * @version 1.5.0
10
+ */
11
+class SFSB
12
+{
13
+	private $SFSclass = null;
14
+
15
+	/*
16
+	 * SMF variables we will load into here for easy reference later.
17
+	*/
18
+	private string $scripturl;
19
+	private array $context;
20
+	private array $smcFunc;
21
+	/* This is array in "theory" only.  SMF sometimes will null this when pulling from cache and causes an error */
22
+	private ?array $modSettings;
23
+	private ?array $user_info;
24
+	private ?array $txt;
25
+
26
+	private int $memID;
27
+	private ?array $user_profile;
28
+
29
+	/**
30
+	 * Creates a self reference to the ASL class for use later.
31
+	 *
32
+	 * @version 1.0
33
+	 * @since 1.0
34
+	 * @return object The SFS Admin class is returned.
35
+	 */
36
+	public static function selfClass(): self
37
+	{
38
+		if (!isset($GLOBALS['context']['instances'][__CLASS__]))
39
+			$GLOBALS['context']['instances'][__CLASS__] = new self();
40
+
41
+		return $GLOBALS['context']['instances'][__CLASS__];
42
+	}
43
+
44
+	/**
45
+	 * Build the class, figure out what software/version we have.
46
+	 * Loads up the defaults.
47
+	 *
48
+	 * @CalledIn SMF 2.0, SMF 2.1
49
+	 * @version 1.5.0
50
+	 * @since 1.0
51
+	 * @return void No return is generated
52
+	 */
53
+	public function __construct()
54
+	{
55
+		$this->scripturl = $GLOBALS['scripturl'];
56
+		foreach (['context', 'smcFunc', 'txt', 'modSettings', 'user_info'] as $f)
57
+			$this->{$f} = &$GLOBALS[$f];
58
+
59
+		$this->SFSclass = &$this->smcFunc['classSFS'];			
60
+	}
61
+
62
+	/**
63
+	 * Automatically add a IP ban.
64
+	 *
65
+	 * @param string $ip_address The IP address of the spammer.
66
+	 *
67
+	 * @CalledIn SMF 2.0, SMF 2.1
68
+	 * @version 1.5.0
69
+	 * @since 1.5.0
70
+	 * @return bool True upon success, false otherwise.
71
+	 */
72
+	public static function AddNewIpBan(string $ip_address): bool
73
+	{
74
+		return (self::selfClass())->BanNewIP($ip_address);
75
+	}
76
+
77
+	/**
78
+	 * Admin wants to create a ban group.
79
+	 *
80
+	 * @param bool $noChecks Skip the sanity checks.
81
+	 *
82
+	 * @CalledIn SMF 2.0, SMF 2.1
83
+	 * @version 1.5.0
84
+	 * @since 1.5.0
85
+	 * @return bool True upon success, false otherwise.
86
+	 */
87
+	public static function AdminCreateBanGroup(bool $noChecks = false): bool
88
+	{
89
+		return (self::selfClass())->createBanGroup($noChecks);
90
+	}
91
+
92
+	/**
93
+	 * They have triggered a automatic IP ban, lets do it.
94
+	 * In newer versions we attempt to use more of the APIs, but fall back as needed.
95
+	 *
96
+	 * @param string $ip_address The IP address of the spammer.
97
+	 *
98
+	 * @internal
99
+	 * @CalledIn SMF 2.0, SMF 2.1
100
+	 * @version 1.5.0
101
+	 * @since 1.0
102
+	 * @return bool True upon success, false otherwise.
103
+	 */
104
+	public function BanNewIP(string $ip_address): bool
105
+	{
106
+		// Did we loose our Ban Group? Try to fix this.
107
+		if (!empty($this->modSettings['sfs_ipcheck_autoban']) && empty($this->modSettings['sfs_ipcheck_autoban_group']))
108
+			$this->createBanGroup();
109
+
110
+		// Still no Ban Group? Bail out.
111
+		if (empty($this->modSettings['sfs_ipcheck_autoban']) || empty($this->modSettings['sfs_ipcheck_autoban_group']))
112
+			return false;
113
+
114
+		$this->SFSclass->loadSources('ManageBans');
115
+
116
+		// Did this work?
117
+		if ($this->doBanNewSpammer($ip_address))
118
+		{
119
+			// Log this.  The log will show from the user/guest and ip of spammer.
120
+			logAction('ban', [
121
+				'ip_range' => $ip_address,
122
+				'new' => 1,
123
+				'source' => 'sfs'
124
+			]);
125
+
126
+			// Let things know we need updated ban data.
127
+			updateSettings(['banLastUpdated' => time()]);
128
+			updateBanMembers();
129
+		}
130
+
131
+		return true;
132
+	}
133
+
134
+	/**
135
+	 * Create a Ban Group if needed to handle automatic IP bans.
136
+	 * We attempt to use the known ban function to create bans, otherwise we just fall back to a standard insert.
137
+	 *
138
+	 * @internal
139
+	 * @CalledIn SMF 2.0, SMF 2.1
140
+	 * @version 1.5.0
141
+	 * @since 1.0
142
+	 * @return bool True upon success, false otherwise.
143
+	 */
144
+	private function createBanGroup(bool $noChecks = false): bool
145
+	{
146
+		// Is this disabled? Don't do it.
147
+		if (empty($noChecks) && empty($this->modSettings['sfs_ipcheck_autoban']))
148
+			return false;
149
+
150
+		$id_ban_group = $this->getBanGroup();
151
+		if (!empty($id_ban_group))
152
+		{
153
+			updateSettings(['sfs_ipcheck_autoban_group' => $ban_data['id_ban_group']]);
154
+			return true;
155
+		}
156
+
157
+		$this->SFSclass->loadSources('ManageBans');
158
+
159
+		// Ban Information, this follows the format from the function.
160
+		$ban_info = [
161
+			'name' => substr($this->SFSclass->txt('sfs_ban_group_name'), 0, 20),
162
+			'cannot' => [
163
+				'access' => 1,
164
+				'register' => 1,
165
+				'post' => 1,
166
+				'login' => 1,
167
+			],
168
+			'db_expiration' => 'NULL',
169
+			'reason' => $this->SFSclass->txt('sfs_ban_group_reason'),
170
+			'notes' => $this->SFSclass->txt('sfs_ban_group_notes')
171
+		];
172
+
173
+		// If we can shortcut this..
174
+		$ban_group_id = 0;
175
+		if (function_exists('insertBanGroup'))
176
+			$ban_group_id = insertBanGroup($ban_info);
177
+
178
+		// Fall back.
179
+		if (is_array($ban_group_id) || empty($ban_group_id))
180
+			$ban_group_id = $this->createBanGroupDirect($ban_info);
181
+
182
+		// Didn't work? Try again later.
183
+		if (empty($ban_group_id))
184
+			return false;
185
+
186
+		updateSettings(['sfs_ipcheck_autoban_group' => $ban_group_id]);
187
+		return true;
188
+	}
189
+
190
+	/**
191
+	 * Try to get the ban group.
192
+	 *
193
+	 * @internal
194
+	 * @CalledIn SMF 2.0, SMF 2.1
195
+	 * @version 1.5.0
196
+	 * @since 1.5.0
197
+	 * @return int The ban group id.
198
+	 */
199
+	private function getBanGroup(): ?int
200
+	{
201
+		$ban_group_id = null;
202
+		// Maybe just got unlinked, if we can find the matching name, relink it.
203
+		$request = $this->smcFunc['db_query']('', '
204
+			SELECT id_ban_group
205
+			FROM {db_prefix}ban_groups
206
+			WHERE name = {string:new_ban_name}
207
+			LIMIT 1',
208
+			[
209
+				'new_ban_name' => substr($this->SFSclass->txt('sfs_ban_group_name'), 0, 20),
210
+			]
211
+		);
212
+		if ($this->smcFunc['db_num_rows']($request) == 1)
213
+		{
214
+			$ban_data = $this->smcFunc['db_fetch_assoc']($request);
215
+			$this->smcFunc['db_free_result']($request);
216
+
217
+			if (!empty($ban_data['id_ban_group']))
218
+			{
219
+				$ban_group_id = $ban_data['id_ban_group'];
220
+				return true;
221
+			}
222
+		}
223
+		$this->smcFunc['db_free_result']($request);
224
+
225
+		return $ban_group_id;
226
+	}
227
+
228
+	/**
229
+	 * We failed to create a ban group via the API, do it manually.
230
+	 *
231
+	 * @param array $ban_info The ban info
232
+	 *
233
+	 * @internal
234
+	 * @CalledIn SMF 2.0, SMF 2.1
235
+	 * @version 1.5.0
236
+	 * @since 1.2
237
+	 * @return bool True upon success, false otherwise.
238
+	 */
239
+	private function createBanGroupDirect(array $ban_info): int
240
+	{
241
+		$this->smcFunc['db_insert']('',
242
+			'{db_prefix}ban_groups',
243
+			[
244
+				'name' => 'string-20', 'ban_time' => 'int', 'expire_time' => 'raw', 'cannot_access' => 'int', 'cannot_register' => 'int',
245
+				'cannot_post' => 'int', 'cannot_login' => 'int', 'reason' => 'string-255', 'notes' => 'string-65534',
246
+			],
247
+			[
248
+				$ban_info['name'], time(), $ban_info['db_expiration'], $ban_info['cannot']['access'], $ban_info['cannot']['register'],
249
+				$ban_info['cannot']['post'], $ban_info['cannot']['login'], $ban_info['reason'], $ban_info['notes'],
250
+			],
251
+			['id_ban_group'],
252
+			1
253
+		);
254
+		return $this->smcFunc['db_insert_id']('{db_prefix}ban_groups', 'id_ban_group');
255
+	}
256
+
257
+	/**
258
+	 * Do the ban logic.  Handle it for SMF 2.1 or 2.0
259
+	 *
260
+	 * @param string $ip_address The IP address of the spammer.
261
+	 *
262
+	 * @internal
263
+	 * @CalledIn SMF 2.0, SMF 2.1
264
+	 * @version 1.5.0
265
+	 * @since 1.5.0
266
+	 * @return bool True upon success, false otherwise.
267
+	 */
268
+	private function doBanNewSpammer(string $ip_address): bool
269
+	{
270
+		// SMF 2.1 has some easier to use logic.
271
+		if (function_exists('addTriggers'))
272
+			return $this->BanNewIPSMF21($ip_address);
273
+		else
274
+			return $this->BanNewIPSMF20($ip_address);
275
+	}
276
+
277
+	/**
278
+	 * Ban a IP with using some functions that exist in SMF 2.1.
279
+	 *
280
+	 * @param string $ip_address The IP address of the spammer.
281
+	 *
282
+	 * @internal
283
+	 * @CalledIn SMF 2.0
284
+	 * @version 1.5.0
285
+	 * @since 1.2
286
+	 * @return bool True upon success, false otherwise.
287
+	 */
288
+	private function BanNewIPSMF21(string $ip_address): bool
289
+	{
290
+		// We don't call checkExistingTriggerIP as it induces a fatal error.
291
+		$request = $this->smcFunc['db_query']('', '
292
+			SELECT bg.id_ban_group, bg.name
293
+			FROM {db_prefix}ban_groups AS bg
294
+			INNER JOIN {db_prefix}ban_items AS bi ON
295
+				(bi.id_ban_group = bg.id_ban_group)
296
+				AND ip_low = {inet:ip_low} AND ip_high = {inet:ip_high}
297
+			LIMIT 1',
298
+			[
299
+				'ip_low' => $ip_address,
300
+				'ip_high' => $ip_address,
301
+			]
302
+		);
303
+		// Alredy exists, bail out.
304
+		if ($this->smcFunc['db_num_rows']($request) != 0)
305
+		{
306
+			$this->smcFunc['db_free_result']($request);
307
+			return false;
308
+		}
309
+
310
+		// The trigger info.
311
+		$triggers = [
312
+			[
313
+				'ip_low' => $ip_address,
314
+				'ip_high' => $ip_address,
315
+			]
316
+		];
317
+
318
+		// Add it.
319
+		addTriggers($this->modSettings['sfs_ipcheck_autoban_group'], $triggers);
320
+
321
+		return true;
322
+	}
323
+
324
+	/**
325
+	 * We need to fall back to standard db inserts to ban a user as the functions don't exist.
326
+	 *
327
+	 * @param string $ip_address The IP address of the spammer.
328
+	 *
329
+	 * @internal
330
+	 * @CalledIn SMF 2.0
331
+	 * @version 1.5.0
332
+	 * @since 1.2
333
+	 * @return bool True upon success, false otherwise.
334
+	 */
335
+	private function BanNewIPSMF20(string $ip_address): bool
336
+	{
337
+		$ip_parts = ip2range($ip_address);
338
+
339
+		// Not valid? Get out.
340
+		if (count($ip_parts) != 4)
341
+			return false;
342
+
343
+		// We don't call checkExistingTriggerIP as it induces a fatal error.
344
+		$request = $this->smcFunc['db_query']('', '
345
+			SELECT bg.id_ban_group, bg.name
346
+			FROM {db_prefix}ban_groups AS bg
347
+			INNER JOIN {db_prefix}ban_items AS bi ON
348
+				(bi.id_ban_group = bg.id_ban_group)
349
+				AND ip_low1 = {int:ip_low1} AND ip_high1 = {int:ip_high1}
350
+				AND ip_low2 = {int:ip_low2} AND ip_high2 = {int:ip_high2}
351
+				AND ip_low3 = {int:ip_low3} AND ip_high3 = {int:ip_high3}
352
+				AND ip_low4 = {int:ip_low4} AND ip_high4 = {int:ip_high4}
353
+			LIMIT 1',
354
+			[
355
+				'ip_low1' => $ip_parts[0]['low'],
356
+				'ip_high1' => $ip_parts[0]['high'],
357
+				'ip_low2' => $ip_parts[1]['low'],
358
+				'ip_high2' => $ip_parts[1]['high'],
359
+				'ip_low3' => $ip_parts[2]['low'],
360
+				'ip_high3' => $ip_parts[2]['high'],
361
+				'ip_low4' => $ip_parts[3]['low'],
362
+				'ip_high4' => $ip_parts[3]['high'],
363
+			]
364
+		);
365
+		// Alredy exists, bail out.
366
+		if ($this->smcFunc['db_num_rows']($request) != 0)
367
+		{
368
+			$this->smcFunc['db_free_result']($request);
369
+			return false;
370
+		}
371
+
372
+		$ban_triggers = [[
373
+			$this->modSettings['sfs_ipcheck_autoban_group'],
374
+			$ip_parts[0]['low'],
375
+			$ip_parts[0]['high'],
376
+			$ip_parts[1]['low'],
377
+			$ip_parts[1]['high'],
378
+			$ip_parts[2]['low'],
379
+			$ip_parts[2]['high'],
380
+			$ip_parts[3]['low'],
381
+			$ip_parts[3]['high'],
382
+			'',
383
+			'',
384
+			0,
385
+		]];
386
+
387
+		$this->smcFunc['db_insert']('',
388
+			'{db_prefix}ban_items',
389
+			[
390
+				'id_ban_group' => 'int', 'ip_low1' => 'int', 'ip_high1' => 'int', 'ip_low2' => 'int', 'ip_high2' => 'int',
391
+				'ip_low3' => 'int', 'ip_high3' => 'int', 'ip_low4' => 'int', 'ip_high4' => 'int', 'hostname' => 'string-255',
392
+				'email_address' => 'string-255', 'id_member' => 'int',
393
+			],
394
+			$ban_triggers,
395
+			['id_ban']
396
+		);
397
+
398
+		return true;
399
+	}
400
+}
0 401
\ No newline at end of file
... ...
@@ -4,38 +4,52 @@
4 4
  * The Logs class for Stop Forum Spam
5 5
  * @package StopForumSpam
6 6
  * @author SleePy <sleepy @ simplemachines (dot) org>
7
- * @copyright 2019
7
+ * @copyright 2023
8 8
  * @license 3-Clause BSD https://opensource.org/licenses/BSD-3-Clause
9
- * @version 1.2
9
+ * @version 1.5.0
10 10
  */
11 11
 class SFSL
12 12
 {
13
-	public static $SFSLclass = null;
14 13
 	private $SFSclass = null;
15 14
 	private $SFSAclass = null;
16 15
 
17 16
 	/**
18 17
 	 * @var string URLS we need to SFS for UI presentation.
19 18
 	 */
20
-	private $urlSFSipCheck = 'https://www.stopforumspam.com/ipcheck/%1$s';
21
-	private $urlSFSsearch = 'https://www.stopforumspam.com/search/%1$s';
19
+	private string $urlSFSipCheck = 'https://www.stopforumspam.com/ipcheck/%1$s';
20
+	private string $urlSFSsearch = 'https://www.stopforumspam.com/search/%1$s';
21
+
22
+	/**
23
+	 * @var string The URL for the admin page.
24
+	 */
25
+	private ?string $adminLogURL = null;
22 26
 
23 27
 	/**
24 28
 	 * @var mixed Search area handling.
25 29
 	 */
26
-	private $sort_order = 'time';
27
-	private $search_types = array();
28
-	private $search_params = array();
29
-	private $search_params_column = '';
30
-	private $search_params_string = null;
31
-	private $search_params_type = null;
32
-	private $canDeleteLogs = false;
33
-	private $logSearch = array();
30
+	private array $search_types = [];
31
+	private /*string|array*/ $search_params = [];
32
+	private array $logSearch = [];
33
+	private string $sort_order = 'time';
34
+	private string $search_params_column = '';
35
+	private ?string $search_params_string = null;
36
+	private ?string $search_params_type = null;
37
+	private bool $canDeleteLogs = false;
34 38
 
35 39
 	/**
36 40
 	 * @var int How long we disable removing logs.
37 41
 	 */
38
-	private $hoursDisabled = 24;
42
+	private int $hoursDisabled = 24;
43
+
44
+	/*
45
+	 * SMF variables we will load into here for easy reference later.
46
+	*/
47
+	private string $scripturl;
48
+	private array $context;
49
+	private array $smcFunc;
50
+	/* This is array in "theory" only.  SMF sometimes will null this when pulling from cache and causes an error */
51
+	private ?array $modSettings;
52
+	private ?array $txt;
39 53
 
40 54
 	/**
41 55
 	 * Creates a self reference to the SFS Log class for use later.
... ...
@@ -44,22 +58,12 @@ class SFSL
44 58
 	 * @since 1.2
45 59
 	 * @return object The SFS Log class is returned.
46 60
 	 */
47
-	public static function selfClass()
61
+	public static function selfClass(): self
48 62
 	{
49
-		global $smcFunc;
63
+		if (!isset($GLOBALS['context']['instances'][__CLASS__]))
64
+			$GLOBALS['context']['instances'][__CLASS__] = new self();
50 65
 
51
-		if (is_null(self::$SFSLclass))
52
-		{
53
-			if (!empty($smcFunc['SFSL']))
54
-				self::$SFSLclass = $smcFunc['SFSL'];
55
-			else
56
-			{
57
-				self::$SFSLclass = new SFSL();
58
-				$smcFunc['SFSL'] = self::$SFSLclass;
59
-			}
60
-		}
61
-
62
-		return self::$SFSLclass;
66
+		return $GLOBALS['context']['instances'][__CLASS__];
63 67
 	}
64 68
 
65 69
 	/**
... ...
@@ -67,16 +71,20 @@ class SFSL
67 71
 	 * Loads up the defaults.
68 72
 	 *
69 73
 	 * @CalledIn SMF 2.0, SMF 2.1
70
-	 * @version 1.2
74
+	 * @version 1.5.0
71 75
 	 * @since 1.2
72 76
 	 * @return void No return is generated
73 77
 	 */
74 78
 	public function __construct()
75 79
 	{
76
-		global $smcFunc;
80
+		$this->scripturl = $GLOBALS['scripturl'];
81
+		foreach (['context', 'smcFunc', 'txt', 'modSettings'] as $f)
82
+			$this->{$f} = &$GLOBALS[$f];
77 83
 
78
-		$this->SFSclass = &$smcFunc['classSFS'];
84
+		$this->SFSclass = &$this->smcFunc['classSFS'];
79 85
 		$this->SFSAclass = SFSA::selfClass();
86
+
87
+		$this->getBaseUrl();
80 88
 	}
81 89
 
82 90
 	/**
... ...
@@ -88,7 +96,7 @@ class SFSL
88 96
 	 * @api
89 97
 	 * @CalledIn SMF 2.1
90 98
 	 * @See SFSA::startupLogs
91
-	 * @version 1.2
99
+	 * @version 1.5.0
92 100
 	 * @since 1.0
93 101
 	 * @uses integrate_manage_logs - Hook SMF2.1
94 102
 	 * @return void No return is generated
... ...
@@ -96,7 +104,7 @@ class SFSL
96 104
 	public static function hook_manage_logs(array &$log_functions): bool
97 105
 	{
98 106
 		// Add our logs sub action.
99
-		$log_functions['sfslog'] = array('SFS-Subs-Logs.php', 'startupLogs');
107
+		$log_functions['sfslog'] = ['StopForumSpam' . DIRECTORY_SEPARATOR . 'SFS-Logs.php', 'SFSL::startupLogs'];
100 108
 
101 109
 		return self::selfClass()->AddToLogMenu($log_functions);
102 110
 	}
... ...
@@ -108,7 +116,7 @@ class SFSL
108 116
 	 *
109 117
 	 * @CalledIn SMF 2.1
110 118
 	 * @See SFSA::startupLogs
111
-	 * @version 1.1
119
+	 * @version 1.5.0
112 120
 	 * @since 1.1
113 121
 	 * @return void No return is generated
114 122
 	 */
... ...
@@ -116,9 +124,9 @@ class SFSL
116 124
 	{
117 125
 		global $context;
118 126
 
119
-		$context[$context['admin_menu_name']]['tab_data']['tabs']['sfslog'] = array(
127
+		$context[$context['admin_menu_name']]['tab_data']['tabs']['sfslog'] = [
120 128
 			'description' => $this->SFSclass->txt('sfs_admin_logs'),
121
-		);
129
+		];
122 130
 
123 131
 		return true;
124 132
 	}
... ...
@@ -153,7 +161,7 @@ class SFSL
153 161
 	 * @CalledIn SMF2.0, SMF 2.1
154 162
 	 * @See SFSA::getSFSLogEntries
155 163
 	 * @See SFSA::getSFSLogEntriesCount
156
-	 * @version 1.2
164
+	 * @version 1.5.0
157 165
 	 * @since 1.0
158 166
 	 * @uses hook_manage_logs - Hook SMF2.1
159 167
 	 * @uses setupModifyModifications - Injected SMF2.0
... ...
@@ -161,17 +169,15 @@ class SFSL
161 169
 	 */
162 170
 	public function loadLogs(bool $return_config = false): array
163 171
 	{
164
-		global $context, $smcFunc, $sourcedir;
165
-
166 172
 		// No Configs.
167 173
 		if ($return_config)
168
-			return array();
174
+			return [];
169 175
 
170
-		loadLanguage('Modlog');
176
+		$this->SFSclass->loadLanguage('Modlog');
171 177
 
172
-		$context['form_url'] = $this->SFSAclass->get('adminLogURL');
173
-		$context['log_url'] = $this->SFSAclass->get('adminLogURL');
174
-		$context['page_title'] = $this->SFSclass->txt('sfs_admin_logs');
178
+		$this->context['form_url'] = $this->adminLogURL;
179
+		$this->context['log_url'] = $this->adminLogURL;
180
+		$this->context['page_title'] = $this->SFSclass->txt('sfs_admin_logs');
175 181
 		$this->canDeleteLogs = allowedTo('admin_forum');
176 182
 
177 183
 		// Remove all..
... ...
@@ -183,19 +189,19 @@ class SFSL
183 189
 		$this->sort_order = isset($_REQUEST['sort']) && isset($sort_types[$_REQUEST['sort']]) ? $_REQUEST['sort'] : 'time';
184 190
 
185 191
 		// Handle searches.
186
-		$this->handleLogSearch($context['log_url']);
192
+		$this->handleLogSearch($this->context['log_url']);
187 193
 
188
-		require_once($sourcedir . '/Subs-List.php');
194
+		$this->SFSclass->loadSources('Subs-List');
189 195
 
190 196
 		$listOptions = $this->loadLogsListOptions();
191 197
 
192 198
 		// Create the watched user list.
193 199
 		createList($listOptions);
194 200
 
195
-		$context['sub_template'] = 'show_list';
196
-		$context['default_list'] = 'sfslog_list';
201
+		$this->context['sub_template'] = 'show_list';
202
+		$this->context['default_list'] = 'sfslog_list';
197 203
 
198
-		return array();
204
+		return [];
199 205
 	}
200 206
 
201 207
 	/**
... ...
@@ -205,26 +211,26 @@ class SFSL
205 211
 	 * @CalledIn SMF2.0, SMF 2.1
206 212
 	 * @See SFSA::getSFSLogEntries
207 213
 	 * @See SFSA::getSFSLogEntriesCount
208
-	 * @version 1.2
214
+	 * @version 1.5.0
209 215
 	 * @since 1.2
210 216
 	 * @return array The list options data
211 217
 	 */
212 218
 	public function loadLogsListOptions(): array
213 219
 	{
214
-		global $context;
220
+		$token = $this->SFSclass->createToken('sfs_logs');
215 221
 
216
-		return array(
222
+		return [
217 223
 			'id' => 'sfslog_list',
218 224
 			'title' => $this->SFSclass->txt('sfs_admin_logs'),
219 225
 			'width' => '100%',
220 226
 			'items_per_page' => '50',
221 227
 			'no_items_label' => $this->SFSclass->txt('sfs_log_no_entries_found'),
222
-			'base_href' => $context['log_url'],
228
+			'base_href' => $this->context['log_url'],
223 229
 			'default_sort_col' => 'time',
224 230
 			'get_items' => $this->loadLogsGetItems(),
225 231
 			'get_count' => $this->loadLogsGetCount(),
226 232
 			// This assumes we are viewing by user.
227
-			'columns' => array(
233
+			'columns' => [
228 234
 				'type' => $this->loadLogsColumnType(),
229 235
 				'time' => $this->loadLogsColumnTime(),
230 236
 				'url' => $this->loadLogsColumnURL(),
... ...
@@ -236,20 +242,21 @@ class SFSL
236 242
 				'checks' => $this->loadLogsColumnChecks(),
237 243
 				'result' => $this->loadLogsColumnResult(),
238 244
 				'delete' => $this->loadLogsColumnDelete(),
239
-			),
240
-			'form' => array(
241
-				'href' => $context['form_url'],
245
+			],
246
+			'form' => [
247
+				'href' => $this->context['form_url'],
242 248
 				'include_sort' => true,
243 249
 				'include_start' => true,
244
-				'hidden_fields' => array(
245
-					$context['session_var'] => $context['session_id'],
250
+				'token' => empty($token) ? null : 'sfs_logs',
251
+				'hidden_fields' => [
252
+					$this->context['session_var'] => $this->context['session_id'],
246 253
 					'params' => $this->search_params
247
-				),
248
-			),
249
-			'additional_rows' => array(
254
+				],
255
+			],
256
+			'additional_rows' => [
250 257
 				$this->loadLogsGetAddtionalRow(),
251
-			),
252
-		);
258
+			],
259
+		];
253 260
 	}
254 261
 
255 262
 	/**
... ...
@@ -257,12 +264,15 @@ class SFSL
257 264
 	 *
258 265
 	 * @internal
259 266
 	 * @CalledIn SMF2.0, SMF 2.1
260
-	 * @version 1.1
267
+	 * @version 1.5.0
261 268
 	 * @since 1.1
262 269
 	 * @return void Nothing is returned, the logs are deleted as requested and admin redirected.
263 270
 	 */
264 271
 	private function handleLogDeletes(): void
265 272
 	{
273
+		checkSession();
274
+		$this->SFSclass->createToken('sfs_logs', 'post');
275
+
266 276
 		if (isset($_POST['removeall']) && $this->canDeleteLogs)
267 277
 			$this->removeAllLogs();
268 278
 		elseif (!empty($_POST['remove']) && isset($_POST['delete']) && $this->canDeleteLogs)
... ...
@@ -274,13 +284,13 @@ class SFSL
274 284
 	 *
275 285
 	 * @internal
276 286
 	 * @CalledIn SMF 2.0, SMF 2.1
277
-	 * @version 1.2
287
+	 * @version 1.5.0
278 288
 	 * @since 1.2
279 289
 	 * @return array The valid Sort Types.
280 290
 	 */
281 291
 	private function handleLogsGetSortTypes(): array
282 292
 	{
283
-		return array(
293
+		return [
284 294
 			'id_type' =>'l.id_type',
285 295
 			'log_time' => 'l.log_time',
286 296
 			'url' => 'l.url',
... ...
@@ -289,7 +299,7 @@ class SFSL
289 299
 			'email' => 'l.email',
290 300
 			'ip' => 'l.ip',
291 301
 			'ip2' => 'l.ip2',
292
-		);
302
+		];
293 303
 	}
294 304
 
295 305
 	/**
... ...
@@ -297,19 +307,19 @@ class SFSL
297 307
 	 *
298 308
 	 * @internal
299 309
 	 * @CalledIn SMF2.0, SMF 2.1
300
-	 * @version 1.1
310
+	 * @version 1.5.0
301 311
 	 * @since 1.1
302 312
 	 * @return array The options for the get_items
303 313
 	 */
304 314
 	private function loadLogsGetItems(): array
305 315
 	{
306
-		return array(
307
-			'function' => array($this, 'getSFSLogEntries'),
308
-			'params' => array(
316
+		return [
317
+			'function' => [$this, 'getSFSLogEntries'],
318
+			'params' => [
309 319
 				(!empty($this->logSearch['string']) ? ' INSTR({raw:sql_type}, {string:search_string})' : ''),
310
-				array('sql_type' => $this->search_params_column, 'search_string' => $this->logSearch['string']),
311
-			),
312
-		);
320
+				['sql_type' => $this->search_params_column, 'search_string' => $this->logSearch['string']],
321
+			],
322
+		];
313 323
 	}
314 324
 
315 325
 	/**
... ...
@@ -317,19 +327,19 @@ class SFSL
317 327
 	 *
318 328
 	 * @internal
319 329
 	 * @CalledIn SMF2.0, SMF 2.1
320
-	 * @version 1.1
330
+	 * @version 1.5.0
321 331
 	 * @since 1.1
322 332
 	 * @return array The options for the get_items
323 333
 	 */
324 334
 	private function loadLogsGetCount(): array
325 335
 	{
326
-		return array(
327
-			'function' => array($this, 'getSFSLogEntriesCount'),
328
-			'params' => array(
336
+		return [
337
+			'function' => [$this, 'getSFSLogEntriesCount'],
338
+			'params' => [
329 339
 				(!empty($this->logSearch['string']) ? ' INSTR({raw:sql_type}, {string:search_string})' : ''),
330
-				array('sql_type' => $this->search_params_column, 'search_string' => $this->logSearch['string']),
331
-			),
332
-		);
340
+				['sql_type' => $this->search_params_column, 'search_string' => $this->logSearch['string']],
341
+			],
342
+		];
333 343
 	}
334 344
 
335 345
 	/**
... ...
@@ -337,49 +347,45 @@ class SFSL
337 347
 	 *
338 348
 	 * @internal
339 349
 	 * @CalledIn SMF2.0, SMF 2.1
340
-	 * @version 1.1
350
+	 * @version 1.5.0
341 351
 	 * @since 1.1
342 352
 	 * @return array The options for the get_items
343 353
 	 */
344 354
 	private function loadLogsGetAddtionalRow(): array
345 355
 	{
346
-		global $smcFunc;
347
-
348
-		return array(
356
+		return [
349 357
 			'position' => 'below_table_data',
350 358
 			'value' => '
351 359
 				' . $this->SFSclass->txt('sfs_log_search') . ' (' . $this->logSearch['label'] . '):
352
-				<input type="text" name="search" size="18" value="' . $smcFunc['htmlspecialchars']($this->logSearch['string']) . '" class="input_text" /> <input type="submit" name="is_search" value="' . $this->SFSclass->txt('modlog_go') . '" class="button_submit" />
360
+				<input type="text" name="search" size="18" value="' . $this->smcFunc['htmlspecialchars']($this->logSearch['string']) . '" class="input_text" /> <input type="submit" name="is_search" value="' . $this->SFSclass->txt('modlog_go') . '" class="button_submit" />
353 361
 				' . ($this->canDeleteLogs ? ' |
354 362
 					<input type="submit" name="remove" value="' . $this->SFSclass->txt('modlog_remove') . '" class="button_submit" />
355 363
 					<input type="submit" name="removeall" value="' . $this->SFSclass->txt('modlog_removeall') . '" class="button_submit" />' : ''),
356
-		);
364
+		];
357 365
 	}
358 366
 
359
-
360 367
 	/**
361 368
 	 * loadLogs - Column - Type.
362 369
 	 *
363 370
 	 * @internal
364 371
 	 * @CalledIn SMF2.0, SMF 2.1
365
-	 * @version 1.1
372
+	 * @version 1.5.0
366 373
 	 * @since 1.1
367 374
 	 * @return array The options for the column
368 375
 	 */
369 376
 	private function loadLogsColumnType(): array
370 377
 	{
371
-		return array(
372
-			'header' => array(
378
+		return [
379
+			'header' => [
373 380
 				'value' => $this->SFSclass->txt('sfs_log_header_type'),
374 381
 				'class' => 'lefttext',
375
-			),
376
-			'data' => array(
382
+			],
383
+			'data' => [
377 384
 				'db' => 'type',
378 385
 				'class' => 'smalltext',
379
-			),
380
-			'sort' => array(
381
-			),
382
-		);
386
+			],
387
+			'sort' => [],
388
+		];
383 389
 	}
384 390
 
385 391
 	/**
... ...
@@ -387,26 +393,26 @@ class SFSL
387 393
 	 *
388 394
 	 * @internal
389 395
 	 * @CalledIn SMF2.0, SMF 2.1
390
-	 * @version 1.1
396
+	 * @version 1.5.0
391 397
 	 * @since 1.1
392 398
 	 * @return array The options for the column
393 399
 	 */
394 400
 	private function loadLogsColumnTime(): array
395 401
 	{
396
-		return array(
397
-			'header' => array(
402
+		return [
403
+			'header' => [
398 404
 				'value' => $this->SFSclass->txt('sfs_log_header_time'),
399 405
 				'class' => 'lefttext',
400
-			),
401
-			'data' => array(
406
+			],
407
+			'data' => [
402 408
 				'db' => 'time',
403 409
 				'class' => 'smalltext',
404
-			),
405
-			'sort' => array(
410
+			],
411
+			'sort' => [
406 412
 				'default' => 'l.log_time DESC',
407 413
 				'reverse' => 'l.log_time',
408
-			),
409
-		);
414
+			],
415
+		];
410 416
 	}
411 417
 
412 418
 	/**
... ...
@@ -414,27 +420,27 @@ class SFSL
414 420
 	 *
415 421
 	 * @internal
416 422
 	 * @CalledIn SMF2.0, SMF 2.1
417
-	 * @version 1.1
423
+	 * @version 1.5.0
418 424
 	 * @since 1.1
419 425
 	 * @return array The options for the column
420 426
 	 */
421 427
 	private function loadLogsColumnURL(): array
422 428
 	{
423
-		return array(
424
-			'header' => array(
429
+		return [
430
+			'header' => [
425 431
 				'value' => $this->SFSclass->txt('sfs_log_header_url'),
426 432
 				'class' => 'lefttext',
427
-			),
428
-			'data' => array(
433
+			],
434
+			'data' => [
429 435
 				'db' => 'url',
430 436
 				'class' => 'smalltext',
431 437
 				'style' => 'word-break: break-word;',
432
-			),
433
-			'sort' => array(
438
+			],
439
+			'sort' => [
434 440
 				'default' => 'l.url DESC',
435 441
 				'reverse' => 'l.url',
436
-			),
437
-		);
442
+			],
443
+		];
438 444
 	}
439 445
 
440 446
 	/**
... ...
@@ -442,26 +448,26 @@ class SFSL
442 448
 	 *
443 449
 	 * @internal
444 450
 	 * @CalledIn SMF2.0, SMF 2.1
445
-	 * @version 1.1
451
+	 * @version 1.5.0
446 452
 	 * @since 1.1
447 453
 	 * @return array The options for the column
448 454
 	 */
449 455
 	private function loadLogsColumnMember(): array
450 456
 	{
451
-		return array(
452
-			'header' => array(
457
+		return [
458
+			'header' => [
453 459
 				'value' => $this->SFSclass->txt('sfs_log_header_member'),
454 460
 				'class' => 'lefttext',
455
-			),
456
-			'data' => array(
461
+			],
462
+			'data' => [
457 463
 				'db' => 'member_link',
458 464
 				'class' => 'smalltext',
459
-			),
460
-			'sort' => array(
465
+			],
466
+			'sort' => [
461 467
 				'default' => 'mem.id_member',
462 468
 				'reverse' => 'mem.id_member DESC',
463
-			),
464
-		);
469
+			],
470
+		];
465 471
 	}
466 472
 
467 473
 	/**
... ...
@@ -469,26 +475,26 @@ class SFSL
469 475
 	 *
470 476
 	 * @internal
471 477
 	 * @CalledIn SMF2.0, SMF 2.1
472
-	 * @version 1.1
478
+	 * @version 1.5.0
473 479
 	 * @since 1.1
474 480
 	 * @return array The options for the column
475 481
 	 */
476 482
 	private function loadLogsColumnUsername(): array
477 483
 	{
478
-		return array(
479
-			'header' => array(
484
+		return [
485
+			'header' => [
480 486
 				'value' => $this->SFSclass->txt('sfs_log_header_username'),
481 487
 				'class' => 'lefttext',
482
-			),
483
-			'data' => array(
488
+			],
489
+			'data' => [
484 490
 				'db' => 'username',
485 491
 				'class' => 'smalltext',
486
-			),
487
-			'sort' => array(
492
+			],
493
+			'sort' => [
488 494
 				'default' => 'l.username',
489 495
 				'reverse' => 'l.username DESC',
490
-			),
491
-		);
496
+			],
497
+		];
492 498
 	}
493 499
 
494 500
 	/**
... ...
@@ -496,26 +502,26 @@ class SFSL
496 502
 	 *
497 503
 	 * @internal
498 504
 	 * @CalledIn SMF2.0, SMF 2.1
499
-	 * @version 1.1
505
+	 * @version 1.5.0
500 506
 	 * @since 1.1
501 507
 	 * @return array The options for the column
502 508
 	 */
503 509
 	private function loadLogsColumnEmail(): array
504 510
 	{
505
-		return array(
506
-			'header' => array(
511
+		return [
512
+			'header' => [
507 513
 				'value' => $this->SFSclass->txt('sfs_log_header_email'),
508 514
 				'class' => 'lefttext',
509
-			),
510
-			'data' => array(
515
+			],
516
+			'data' => [
511 517
 				'db' => 'email',
512 518
 				'class' => 'smalltext',
513
-			),
514
-			'sort' => array(
519
+			],
520
+			'sort' => [
515 521
 				'default' => 'l.email',
516 522
 				'reverse' => 'l.email DESC',
517
-			),
518
-		);
523
+			],
524
+		];
519 525
 	}
520 526
 
521 527
 	/**
... ...
@@ -524,26 +530,26 @@ class SFSL
524 530
 	 * @param string $ip2 If true, use ip2
525 531
 	 * @internal
526 532
 	 * @CalledIn SMF2.0, SMF 2.1
527
-	 * @version 1.1
533
+	 * @version 1.5.0
528 534
 	 * @since 1.1
529 535
 	 * @return array The options for the column
530 536
 	 */
531 537
 	private function loadLogsColumnIP(bool $ip2 = false): array
532 538
 	{
533
-		return array(
534
-			'header' => array(
539
+		return [
540
+			'header' => [
535 541
 				'value' => $this->SFSclass->txt('sfs_log_header_ip' . ($ip2 ? '2' : '')),
536 542
 				'class' => 'lefttext',
537
-			),
538
-			'data' => array(
543
+			],
544
+			'data' => [
539 545
 				'db' => 'ip' . ($ip2 ? '2' : ''),
540 546
 				'class' => 'smalltext',
541
-			),
542
-			'sort' => array(
547
+			],
548
+			'sort' => [
543 549
 				'default' => 'l.ip' . ($ip2 ? '2' : ''),
544 550
 				'reverse' => 'l.ip' . ($ip2 ? '2' : '') . ' DESC',
545
-			),
546
-		);
551
+			],
552
+		];
547 553
 	}
548 554
 
549 555
 	/**
... ...
@@ -551,24 +557,24 @@ class SFSL
551 557
 	 *
552 558
 	 * @internal
553 559
 	 * @CalledIn SMF2.0, SMF 2.1
554
-	 * @version 1.1
560
+	 * @version 1.5.0
555 561
 	 * @since 1.1
556 562
 	 * @return array The options for the column
557 563
 	 */
558 564
 	private function loadLogsColumnChecks(): array
559 565
 	{
560
-		return array(
561
-			'header' => array(
566
+		return [
567
+			'header' => [
562 568
 				'value' => $this->SFSclass->txt('sfs_log_checks'),
563 569
 				'class' => 'lefttext',
564
-			),
565
-			'data' => array(
570
+			],
571
+			'data' => [
566 572
 				'db' => 'checks',
567 573
 				'class' => 'smalltext',
568 574
 				'style' => 'word-break: break-word;',
569
-			),
570
-			'sort' => array(),
571
-		);
575
+			],
576
+			'sort' => [],
577
+		];
572 578
 	}
573 579
 
574 580
 	/**
... ...
@@ -576,24 +582,24 @@ class SFSL
576 582
 	 *
577 583
 	 * @internal
578 584
 	 * @CalledIn SMF2.0, SMF 2.1
579
-	 * @version 1.1
585
+	 * @version 1.5.0
580 586
 	 * @since 1.1
581 587
 	 * @return array The options for the column
582 588
 	 */
583 589
 	private function loadLogsColumnResult(): array
584 590
 	{
585
-		return array(
586
-			'header' => array(
591
+		return [
592
+			'header' => [
587 593
 				'value' => $this->SFSclass->txt('sfs_log_result'),
588 594
 				'class' => 'lefttext',
589
-			),
590
-			'data' => array(
595
+			],
596
+			'data' => [
591 597
 				'db' => 'result',
592 598
 				'class' => 'smalltext',
593 599
 				'style' => 'word-break: break-word;',
594
-			),
595
-			'sort' => array(),
596
-		);
600
+			],
601
+			'sort' => [],
602
+		];
597 603
 	}
598 604
 
599 605
 	/**
... ...
@@ -601,24 +607,24 @@ class SFSL
601 607
 	 *
602 608
 	 * @internal
603 609
 	 * @CalledIn SMF2.0, SMF 2.1
604
-	 * @version 1.1
610
+	 * @version 1.5.0
605 611
 	 * @since 1.1
606 612
 	 * @return array The options for the column
607 613
 	 */
608 614
 	private function loadLogsColumnDelete(): array
609 615
 	{
610
-		return array(
611
-			'header' => array(
616
+		return [
617
+			'header' => [
612 618
 				'value' => '<input type="checkbox" name="all" class="input_check" onclick="invertAll(this, this.form);" />',
613
-			),
614
-			'data' => array(
619
+			],
620
+			'data' => [
615 621
 				'function' => function($entry)
616 622
 				{
617 623
 					return '<input type="checkbox" class="input_check" name="delete[]" value="' . $entry['id'] . '"' . ($entry['editable'] ? '' : ' disabled="disabled"') . ' />';
618 624
 				},
619 625
 				'style' => 'text-align: center;',
620
-			),
621
-		);
626
+			],
627
+		];
622 628
 	}
623 629
 
624 630
 	/**
... ...
@@ -633,18 +639,16 @@ class SFSL
633 639
 	 * @api
634 640
 	 * @CalledIn SMF 2.0, SMF 2.1
635 641
 	 * @See SFSA::loadLogs
636
-	 * @version 1.2
642
+	 * @version 1.5.0
637 643
 	 * @since 1.0
638 644
 	 * @uses hook_manage_logs - Hook SMF2.1
639 645
 	 * @uses setupModifyModifications - Injected SMF2.0
640 646
 	 * @return void No return is generated
641 647
 	 */
642
-	public function getSFSLogEntries(int $start, int $items_per_page, string $sort, string $query_string = '', array $query_params = array()): array
648
+	public function getSFSLogEntries(int $start, int $items_per_page, string $sort, string $query_string = '', array $query_params = []): array
643 649
 	{
644
-		global $scripturl, $context, $smcFunc;
645
-
646 650
 		// Fetch all of our logs.
647
-		$result = $smcFunc['db_query']('', '
651
+		$result = $this->smcFunc['db_query']('', '
648 652
 			SELECT
649 653
 				l.id_sfs, l.id_type, l.log_time, l.url, l.id_member, l.username, l.email, l.ip, l.ip2, l.checks, l.result,
650 654
 				mem.real_name, mg.group_name
... ...
@@ -656,17 +660,17 @@ class SFSL
656 660
 					AND ' . $query_string : '') . '
657 661
 			ORDER BY ' . $sort . '
658 662
 			LIMIT {int:start}, {int:items_per_page}',
659
-			array_merge($query_params, array(
663
+			array_merge($query_params, [
660 664
 				'start' => $start,
661 665
 				'items_per_page' => $items_per_page,
662 666
 				'reg_group_id' => 0,
663
-			))
667
+			])
664 668
 		);
665 669
 
666
-		$entries = array();
667
-		while ($row = $smcFunc['db_fetch_assoc']($result))
670
+		$entries = [];
671
+		while ($row = $this->smcFunc['db_fetch_assoc']($result))
668 672
 			$entries[$row['id_sfs']] = $this->getSFSLogPrepareEntry($row);
669
-		$smcFunc['db_free_result']($result);
673
+		$this->smcFunc['db_free_result']($result);
670 674
 
671 675
 		return $entries;
672 676
 	}
... ...
@@ -679,21 +683,19 @@ class SFSL
679 683
 	 * @api
680 684
 	 * @CalledIn SMF 2.0, SMF 2.1
681 685
 	 * @See SFSA::getSFSLogEntries
682
-	 * @version 1.2
686
+	 * @version 1.5.0
683 687
 	 * @since 1.2
684 688
 	 * @return array An array of data ready to be sent to output
685 689
 	 */
686
-	public function getSFSLogPrepareEntry(array $row = array()): array
690
+	public function getSFSLogPrepareEntry(array $row = []): array
687 691
 	{
688
-		global $scripturl;
689
-
690
-		$return = array(
692
+		$return = [
691 693
 			'id' => $row['id_sfs'],
692 694
 			'type' => $this->SFSclass->txt('sfs_log_types_' . $row['id_type']),
693 695
 			'time' => timeformat($row['log_time']),
694 696
 			'url' => preg_replace('~http(s)?://~i', 'hxxp\\1://', $row['url']),
695 697
 			'timestamp' => $row['log_time'],
696
-			'member_link' => $row['id_member'] ? '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>' : (empty($row['real_name']) ? ($this->SFSclass->txt('guest') . (!empty($row['extra']['member_acted']) ? ' (' . $row['extra']['member_acted'] . ')' : '')) : $row['real_name']),
698
+			'member_link' => $row['id_member'] ? '<a href="' . $this->scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>' : (empty($row['real_name']) ? ($this->SFSclass->txt('guest') . (!empty($row['extra']['member_acted']) ? ' (' . $row['extra']['member_acted'] . ')' : '')) : $row['real_name']),
697 699
 			'username' => $row['username'],
698 700
 			'email' => $row['email'],
699 701
 			'ip' => '<a href="' . sprintf($this->urlSFSipCheck, $row['ip']) . '">' . $row['ip'] . '</a>',
... ...
@@ -701,7 +703,7 @@ class SFSL
701 703
 			'editable' => true, //time() > $row['log_time'] + $this->hoursDisabled * 3600,
702 704
 			'checks_raw' => $row['checks'],
703 705
 			'result_raw' => $row['result'],
704
-		);
706
+		];
705 707
 
706 708
 		$return['checks'] = $this->getSFSLogPrepareEntryChecks($row);
707 709
 		$return['result'] = $this->getSFSLogPrepareEntryResult($row);
... ...
@@ -752,29 +754,26 @@ class SFSL
752 754
 	 * @api
753 755
 	 * @CalledIn SMF 2.0, SMF 2.1
754 756
 	 * @See SFSA::getSFSLogEntries
755
-	 * @version 1.2
757
+	 * @version 1.5.0
756 758
 	 * @since 1.2
757 759
 	 * @return string The formated results entry.
758 760
 	 */
759
-	public function getSFSLogPrepareEntryResult(array $row = array()): string
761
+	public function getSFSLogPrepareEntryResult(array $row = []): string
760 762
 	{
761 763
 		// This tells us what it matched on exactly.
762
-		if (strpos($row['result'], ',') !== false)
763
-		{
764
-			$results = array();
765
-			$multiMatch = explode('|', $row['result'] . '|');
766
-			foreach ($multiMatch as $match)
767
-			{
768
-				if (empty($match))
769
-					continue;
764
+		if (strpos($row['result'], ',') === false)
765
+			return $row['result'];
770 766
 
767
+		$results = [];
768
+		foreach (array_filter(explode('|', $row['result'] . '|'), function ($match) {return !empty($match);}) as $match)
769
+		{
771 770
 			list($resultType, $resultMatch, $extra) = explode(',', $match . ',,,');
772 771
 			$res = sprintf($this->SFSclass->txt('sfs_log_matched_on'), $resultType, $resultMatch);
773 772
 
774 773
 			// If this was a IP ban, note it.
775 774
 			if ($resultType == 'ip' && !empty($extra))
776 775
 				$res .= ' ' . $this->SFSclass->txt('sfs_log_auto_banned');
777
-				if ($resultType == 'username' && !empty($extra))
776
+			elseif ($resultType == 'username' && !empty($extra))
778 777
 				$res .= ' ' . sprintf($this->SFSclass->txt('sfs_log_confidence'), $extra);
779 778
 
780 779
 			$results[] = $res;
... ...
@@ -783,9 +782,6 @@ class SFSL
783 782
 		return implode('<br>', $results);
784 783
 	}
785 784
 
786
-		return $row['result'];
787
-	}
788
-
789 785
 	/**
790 786
 	 * Get the log counts and returns it ready to go for GenericList handling.
791 787
 	 *
... ...
@@ -795,17 +791,15 @@ class SFSL
795 791
 	 * @api
796 792
 	 * @CalledIn SMF 2.0, SMF 2.1
797 793
 	 * @See SFSA::loadLogs
798
-	 * @version 1.0
794
+	 * @version 1.5.0
799 795
 	 * @since 1.0
800 796
 	 * @uses hook_manage_logs - Hook SMF2.1
801 797
 	 * @uses setupModifyModifications - Injected SMF2.0
802 798
 	 * @return void No return is generated
803 799
 	 */
804
-	public function getSFSLogEntriesCount(string $query_string = '', array $query_params = array()): int
800
+	public function getSFSLogEntriesCount(string $query_string = '', array $query_params = []): int
805 801
 	{
806
-		global $smcFunc, $user_info;
807
-
808
-		$result = $smcFunc['db_query']('', '
802
+		$result = $this->smcFunc['db_query']('', '
809 803
 			SELECT COUNT(*)
810 804
 			FROM {db_prefix}log_sfs AS l
811 805
 				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = l.id_member)
... ...
@@ -813,38 +807,55 @@ class SFSL
813 807
 				WHERE id_type IS NOT NULL'
814 808
 				. (!empty($query_string) ? '
815 809
 					AND ' . $query_string : ''),
816
-			array_merge($query_params, array(
810
+			array_merge($query_params, [
817 811
 				'reg_group_id' => 0,
818
-			))
812
+			])
819 813
 		);
820
-		list ($entry_count) = $smcFunc['db_fetch_row']($result);
821
-		$smcFunc['db_free_result']($result);
814
+		list ($entry_count) = $this->smcFunc['db_fetch_row']($result);
815
+		$this->smcFunc['db_free_result']($result);
822 816
 
823 817
 		return (int) $entry_count;
824 818
 	}
825 819
 
820
+	/**
821
+	 * Get our Base url for the form.
822
+	 *
823
+	 * @internal
824
+	 * @CalledIn SMF 2.0, SMF 2.1
825
+	 * @version 1.5.0
826
+	 * @since 1.5.0
827
+	 * @return string The log url.
828
+	 */
829
+	private function getBaseUrl(): string
830
+	{
831
+		if (empty($this->adminLogURL))
832
+		{
833
+			if ($this->SFSclass->versionCheck('2.0', 'smf'))
834
+				$this->adminLogURL = $this->scripturl . '?action=admin;area=modsettings;sa=sfslog';
835
+			else
836
+				$this->adminLogURL = $this->scripturl . '?action=admin;area=logs;sa=sfslog';
837
+		}
838
+		return $this->adminLogURL;
839
+	}
840
+
826 841
 	/**
827 842
 	 * Remove all logs, except those less than 24 hours old.
828 843
 	 *
829 844
 	 * @api
830 845
 	 * @CalledIn SMF 2.0, SMF 2.1
831 846
 	 * @See SFSA::loadLogs
832
-	 * @version 1.0
847
+	 * @version 1.5.0
833 848
 	 * @since 1.0
834 849
 	 * @return void No return is generated
835 850
 	 */
836 851
 	private function removeAllLogs(): void
837 852
 	{
838
-		global $smcFunc;
839
-
840
-		checkSession();
841
-
842
-		$smcFunc['db_query']('', '
853
+		$this->smcFunc['db_query']('', '
843 854
 			DELETE FROM {db_prefix}log_sfs
844 855
 			WHERE log_time < {int:twenty_four_hours_wait}',
845
-			array(
856
+			[
846 857
 				'twenty_four_hours_wait' => time() - $this->hoursDisabled * 3600,
847
-			)
858
+			]
848 859
 		);
849 860
 	}
850 861
 
... ...
@@ -856,24 +867,20 @@ class SFSL
856 867
 	 * @api
857 868
 	 * @CalledIn SMF 2.0, SMF 2.1
858 869
 	 * @See SFSA::loadLogs
859
-	 * @version 1.0
870
+	 * @version 1.5.0
860 871
 	 * @since 1.0
861 872
 	 * @return void No return is generated
862 873
 	 */
863 874
 	private function removeLogs(array $entries): void
864 875
 	{
865
-		global $smcFunc;
866
-
867
-		checkSession();
868
-
869
-		$smcFunc['db_query']('', '
876
+		$this->smcFunc['db_query']('', '
870 877
 			DELETE FROM {db_prefix}log_sfs
871 878
 			WHERE id_sfs IN ({array_string:delete_actions})
872 879
 				AND log_time < {int:twenty_four_hours_wait}',
873
-			array(
880
+			[
874 881
 				'twenty_four_hours_wait' => time() - $this->hoursDisabled * 3600,
875 882
 				'delete_actions' => $entries,
876
-			)
883
+			]
877 884
 		);
878 885
 	}
879 886
 
... ...
@@ -883,14 +890,12 @@ class SFSL
883 890
 	 * @param string $url The base_href
884 891
 	 * @internal
885 892
 	 * @CalledIn SMF 2.0, SMF 2.1
886
-	 * @version 1.0
893
+	 * @version 1.5.0
887 894
 	 * @since 1.0
888 895
 	 * @return void No return is generated here.
889 896
 	 */
890 897
 	private function handleLogSearch(string &$url): void
891 898
 	{
892
-		global $context, $txt;
893
-
894 899
 		// If we have some data from a search, lets bring it back out.
895 900
 		$this->search_params = $this->handleLogSearchParams();
896 901
 
... ...
@@ -902,15 +907,15 @@ class SFSL
902 907
 		$this->search_params_column = $this->search_types[$this->search_params_type]['sql'];
903 908
 
904 909
 		// Setup the search context.
905
-		$this->search_params = empty($this->search_params_string) ? '' : base64_encode(json_encode(array(
910
+		$this->search_params = empty($this->search_params_string) ? '' : base64_encode(json_encode([
906 911
 			'string' => $this->search_params_string,
907 912
 			'type' => $this->search_params_type,
908
-		)));
909
-		$this->logSearch = array(
913
+		]));
914
+		$this->logSearch = [
910 915
 			'string' => $this->search_params_string,
911 916
 			'type' => $this->search_params_type,
912 917
 			'label' => $this->search_types[$this->search_params_type]['label'],
913
-		);
918
+		];
914 919
 
915 920
 		if (!empty($this->search_params))
916 921
 			$url .= ';params=' . $this->search_params;
... ...
@@ -921,7 +926,7 @@ class SFSL
921 926
 	 *
922 927
 	 * @internal
923 928
 	 * @CalledIn SMF 2.0, SMF 2.1
924
-	 * @version 1.1
929
+	 * @version 1.5.0
925 930
 	 * @since 1.0
926 931
 	 * @return bool True upon success, false otherwise.
927 932
 	 */
... ...
@@ -930,14 +935,14 @@ class SFSL
930 935
 		// If we have something to search for saved, get it back out.
931 936
 		if (!empty($_REQUEST['params']) && empty($_REQUEST['is_search']))
932 937
 		{
933
-			$search_params = base64_decode(strtr($_REQUEST['params'], array(' ' => '+')));
938
+			$search_params = base64_decode(strtr($_REQUEST['params'], [' ' => '+']));
934 939
 			$search_params = $this->SFSclass->decodeJSON($search_params);
935 940
 
936 941
 			if (!empty($search_params))
937 942
 				return $search_params;
938 943
 		}
939 944
 
940
-		return array();
945
+		return [];
941 946
 	}
942 947
 
943 948
 	/**
... ...
@@ -945,20 +950,20 @@ class SFSL
945 950
 	 *
946 951
 	 * @internal
947 952
 	 * @CalledIn SMF 2.0, SMF 2.1
948
-	 * @version 1.2
953
+	 * @version 1.5.0
949 954
 	 * @since 1.0
950 955
 	 * @return array The valid Search Types.
951 956
 	 */
952 957
 	private function handleLogSearchTypes(): array
953 958
 	{
954
-		return array(
955
-			'url' => array('sql' => 'l.url', 'label' => $this->SFSclass->txt('sfs_log_search_url')),
956
-			'member' => array('sql' => 'mem.real_name', 'label' => $this->SFSclass->txt('sfs_log_search_member')),
957
-			'username' => array('sql' => 'l.username', 'label' => $this->SFSclass->txt('sfs_log_search_username')),
958
-			'email' => array('sql' => 'l.email', 'label' => $this->SFSclass->txt('sfs_log_search_email')),
959
-			'ip' => array('sql' => 'lm.ip', 'label' => $this->SFSclass->txt('sfs_log_search_ip')),
960
-			'ip2' => array('sql' => 'lm.ip2', 'label' => $this->SFSclass->txt('sfs_log_search_ip2'))
961
-		);
959
+		return [
960
+			'url' => ['sql' => 'l.url', 'label' => $this->SFSclass->txt('sfs_log_search_url')],
961
+			'member' => ['sql' => 'mem.real_name', 'label' => $this->SFSclass->txt('sfs_log_search_member')],
962
+			'username' => ['sql' => 'l.username', 'label' => $this->SFSclass->txt('sfs_log_search_username')],
963
+			'email' => ['sql' => 'l.email', 'label' => $this->SFSclass->txt('sfs_log_search_email')],
964
+			'ip' => ['sql' => 'lm.ip', 'label' => $this->SFSclass->txt('sfs_log_search_ip')],
965
+			'ip2' => ['sql' => 'lm.ip2', 'label' => $this->SFSclass->txt('sfs_log_search_ip2')]
966
+		];
962 967
 	}
963 968
 
964 969
 	/**
... ...
@@ -966,16 +971,18 @@ class SFSL
966 971
 	 *
967 972
 	 * @internal
968 973
 	 * @CalledIn SMF 2.0, SMF 2.1
969
-	 * @version 1.1
974
+	 * @version 1.5.0
970 975
 	 * @since 1.0
971 976
 	 * @return string What we are searching for, validated and cleaned.
972 977
 	 */
973 978
 	private function handleLogSearchParamsString(): string
974 979
 	{
975
-		if (!isset($this->search_params['string']) || (!empty($_REQUEST['search']) && $this->search_params['string'] != $_REQUEST['search']))
976
-			return empty($_REQUEST['search']) ? '' : $_REQUEST['search'];
977
-		else
980
+		if (!empty($_REQUEST['search']) && ($this->search_params['string'] ?? '') != $_REQUEST['search'])
981
+			return (string) $_REQUEST['search'];
982
+		elseif (isset($this->search_params['string']))
978 983
 			return $this->search_params['string'];
984
+		else
985
+			return '';
979 986
 	}
980 987
 
981 988
 	/**
... ...
@@ -983,22 +990,19 @@ class SFSL
983 990
 	 *
984 991
 	 * @internal
985 992
 	 * @CalledIn SMF 2.0, SMF 2.1
986
-	 * @version 1.1
993
+	 * @version 1.5.0
987 994
 	 * @since 1.0
988 995
 	 * @return string The column we are searching.
989 996
 	 */
990 997
 	private function handleLogSearchParamsType(): string
991
-	{
992
-		global $context;
993
-
994
-		if (isset($_REQUEST['search_type']) || empty($this->search_params['type']) || !isset($this->search_types[$this->search_params['type']]))
995 998
 	{
996 999
 		if (isset($_REQUEST['search_type']) && isset($this->search_types[$_REQUEST['search_type']]))
997 1000
 			return (string) $_REQUEST['search_type'];
998
-			if (isset($this->search_types[$this->sort_order]))
1001
+		elseif (!empty($this->search_params['type']) && isset($this->search_types[$this->search_params['type']]))
1002
+			return $this->search_params['type'];
1003
+		elseif (isset($this->search_types[$this->sort_order]))
999 1004
 			return $this->sort_order;
1005
+		else
1000 1006
 			 return 'member';
1001 1007
 	}
1002
-		return $this->search_params['type'];
1003
-	}
1004 1008
 }
1005 1009
\ No newline at end of file
... ...
@@ -0,0 +1,295 @@
1
+<?php
2
+
3
+/**
4
+ * The Profile class for Stop Forum Spam
5
+ * @package StopForumSpam
6
+ * @author SleePy <sleepy @ simplemachines (dot) org>
7
+ * @copyright 2023
8
+ * @license 3-Clause BSD https://opensource.org/licenses/BSD-3-Clause
9
+ * @version 1.5.0
10
+ */
11
+class SFSP
12
+{
13
+	private $SFSclass = null;
14
+
15
+	/*
16
+	 * SMF variables we will load into here for easy reference later.
17
+	*/
18
+	private string $scripturl;
19
+	private array $context;
20
+	private array $smcFunc;
21
+	/* This is array in "theory" only.  SMF sometimes will null this when pulling from cache and causes an error */
22
+	private ?array $modSettings;
23
+	private ?array $user_info;
24
+	private ?array $txt;
25
+
26
+	private int $memID;
27
+	private ?array $user_profile;
28
+
29
+	/**
30
+	 * Creates a self reference to the ASL class for use later.
31
+	 *
32
+	 * @version 1.0
33
+	 * @since 1.0
34
+	 * @return object The SFS Admin class is returned.
35
+	 */
36
+	public static function selfClass(): self
37
+	{
38
+		if (!isset($GLOBALS['context']['instances'][__CLASS__]))
39
+			$GLOBALS['context']['instances'][__CLASS__] = new self();
40
+
41
+		return $GLOBALS['context']['instances'][__CLASS__];
42
+	}
43
+
44
+	/**
45
+	 * Build the class, figure out what software/version we have.
46
+	 * Loads up the defaults.
47
+	 *
48
+	 * @CalledIn SMF 2.0, SMF 2.1
49
+	 * @version 1.5.0
50
+	 * @since 1.0
51
+	 * @return void No return is generated
52
+	 */
53
+	public function __construct()
54
+	{
55
+		$this->scripturl = $GLOBALS['scripturl'];
56
+		foreach (['context', 'smcFunc', 'txt', 'modSettings', 'user_info'] as $f)
57
+			$this->{$f} = &$GLOBALS[$f];
58
+
59
+		$this->SFSclass = &$this->smcFunc['classSFS'];			
60
+	}
61
+
62
+	/**
63
+	 * The hook to setup profile menu.
64
+	 *
65
+	 * @param array $profile_areas All the profile areas.
66
+	 *
67
+	 * @api
68
+	 * @CalledIn SMF 2.1
69
+	 * @See SFS::setupProfileMenu
70
+	 * @version 1.1
71
+	 * @since 1.1
72
+	 * @uses integrate_pre_profile_areas - Hook SMF2.1
73
+	 * @return void the passed $profile_areas is modified.
74
+	 */
75
+	public static function hook_pre_profile_areas(array &$profile_areas): void
76
+	{
77
+		(self::selfClass())->setupProfileMenu($profile_areas);
78
+	}
79
+
80
+	/**
81
+	 * The hook to setup profile menu.
82
+	 *
83
+	 * @param array $profile_areas All the profile areas.
84
+	 *
85
+	 * @api
86
+	 * @CalledIn SMF 2.1
87
+	 * @version 1.5.0
88
+	 * @since 1.1
89
+	 * @uses integrate_pre_profile_areas - Hook SMF2.1
90
+	 * @return void the passed $profile_areas is modified.
91
+	 */
92
+	public function setupProfileMenu(array &$profile_areas): void
93
+	{
94
+		$profile_areas['info']['areas']['sfs'] = [
95
+			'label' => $this->SFSclass->txt('sfs_profile'),
96
+			'file' => 'StopForumSpam' . DIRECTORY_SEPARATOR . 'SFS-Profile.php',
97
+			'icon' => 'sfs.webp',
98
+			'function' => 'SFSP::ProfileTrackSFS',
99
+			'permission' => [
100
+				'own' => ['moderate_forum'],
101
+				'any' => ['moderate_forum'],
102
+			],
103
+		];
104
+
105
+		// SMF 2.0 can't call objects or classes.
106
+		if ($this->SFSclass->versionCheck('2.0', 'smf'))
107
+		{
108
+			function ProfileTrackSFS20(int $memID)
109
+			{
110
+				SFSP::ProfileTrackSFS($memID);
111
+			}
112
+			$profile_areas['info']['areas']['sfs']['function'] = 'ProfileTrackSFS20';
113
+		}
114
+	}
115
+
116
+	/**
117
+	 * The caller for a profile check.
118
+	 *
119
+	 * @param int $memID The id of the member we are checking.
120
+	 *
121
+	 * @api
122
+	 * @CalledIn SMF 2.1
123
+	 * @version 1.1
124
+	 * @since 1.1
125
+	 * @return void the passed $profile_areas is modified.
126
+	 */
127
+	public static function ProfileTrackSFS(int $memID): void
128
+	{
129
+		(self::selfClass())->TrackSFS($memID);
130
+	}
131
+
132
+	/**
133
+	 * Setup the User Profile data for later..
134
+	 *
135
+	 * @param int $memID The current profile.
136
+	 *
137
+	 * @api
138
+	 * @CalledIn SMF 2.0, SMF 2.1
139
+	 * @version 1.5.0
140
+	 * @since 1.1
141
+	 * @uses integrate_pre_profile_areas - Hook SMF2.1
142
+	 * @return void the passed $profile_areas is modified.
143
+	 */
144
+	public function loadUser(int $memID): void
145
+	{
146
+		$this->user_profile = $GLOBALS['user_profile'][$memID] ?? null;
147
+		$this->memID = $memID;
148
+	}
149
+
150
+	/**
151
+	 * The caller for a profile check.
152
+	 *
153
+	 * @param int $memID The id of the member we are checking.
154
+	 *
155
+	 * @api
156
+	 * @CalledIn SMF 2.1
157
+	 * @version 1.5.0
158
+	 * @since 1.1
159
+	 * @return void the passed $profile_areas is modified.
160
+	 */
161
+	public function TrackSFS(int $memID): void
162
+	{
163
+		$this->loadUser($memID);
164
+
165
+		isAllowedTo('moderate_forum');
166
+
167
+		// We need this stuff.
168
+		$this->context['sfs_allow_submit'] = !empty($this->modSettings['sfs_enablesubmission']) && !empty($this->modSettings['sfs_apikey']);
169
+		$this->context['token_check'] = 'sfs_submit-' . $this->memID;
170
+		$cache_key = 'sfs_check_member-' . $this->memID;
171
+
172
+		// Do we have a message?
173
+		$poster_name = null;
174
+		$poster_email = null;
175
+		$poster_ip = null;
176
+		$post_body = null;
177
+		if (isset($_GET['msg']) && intval($_GET['msg']) > 0)
178
+		{
179
+			$row = $this->TrackSFSMessage((int) $_GET['msg']);
180
+			$cache_key .= '-msg' . ((int) $_GET['msg']);
181
+		}
182
+
183
+		$this->context['reason'] = $this->smcFunc['htmlspecialchars']($row['post_body'] ?? '');
184
+
185
+		// Are we submitting this?
186
+		if ($this->context['sfs_allow_submit'] && (isset($_POST['sfs_submit']) || isset($_POST['sfs_submitban'])))
187
+		{
188
+			checkSession();
189
+			$this->SFSclass->validateToken($this->context['token_check'], 'post');
190
+
191
+			$data = [
192
+				'username' => $row['poster_name'] ?? $this->user_profile['real_name'],
193
+				'email' => $row['poster_email'] ?? $this->user_profile['email_address'],
194
+				'ip_addr' => $row['poster_ip'] ?? $this->user_profile['member_ip'],
195
+				'api_key' => $this->modSettings['sfs_apikey']
196
+			];
197
+			$this->TrackSFSSubmit($data);
198
+		}
199
+
200
+		// Check if we have this info.
201
+		if (($cache = cache_get_data($cache_key)) === null || ($response = $this->SFSclass->decodeJSON((string) $cache)) === null)
202
+		{
203
+			$checks = [
204
+				['username' => $row['poster_name'] ?? $this->user_profile['real_name']],
205
+				['email' => $row['poster_email'] ?? $this->user_profile['email_address']],
206
+				['ip' => $row['poster_ip'] ?? $this->user_profile['member_ip']],
207
+				['ip' => $this->user_profile['member_ip2']],
208
+			];
209
+
210
+			$response = (array) $this->SFSclass->SendSFS($checks, 'profile');
211
+			cache_put_data($cache_key, $this->SFSclass->encodeJSON($response), 600);
212
+		}
213
+
214
+		// Prepare for the template.
215
+		$this->context['sfs_overall'] = (bool) $response['success'];
216
+		$this->context['sfs_checks'] = $response;
217
+		unset($this->context['sfs_checks']['success']);
218
+
219
+		if ($this->context['sfs_allow_submit'])
220
+		{
221
+			$this->context['sfs_submit_url'] = $this->scripturl . '?action=profile;area=sfs;u=' . $memID;
222
+			if (is_null($this->SFSclass->createToken($this->context['token_check'], 'post')))
223
+				unset($this->context['token_check']);
224
+		}
225
+
226
+		$this->SFSclass->loadTemplate('StopForumSpam');
227
+		$this->context['sub_template'] = 'profile_tracksfs';
228
+	}
229
+
230
+	/**
231
+	 * Get data from a message we are tracking.
232
+	 *
233
+	 * @internal
234
+	 * @CalledIn SMF 2.0, SMF 2.1
235
+	 * @version 1.5.0
236
+	 * @since 1.5.0
237
+	 * @return ?array Poster data.
238
+	 */
239
+	private function TrackSFSMessage(int $id_msg): ?array
240
+	{
241
+		$row = null;
242
+
243
+		$request = $this->smcFunc['db_query']('', '
244
+			SELECT poster_name, poster_email, poster_ip, body
245
+			FROM {db_prefix}messages
246
+			WHERE id_msg = {int:id_msg}
247
+				AND (
248
+					id_member = {int:id_member}
249
+					OR id_member = 0
250
+				)
251
+				AND {query_see_message_board}
252
+			',
253
+			[
254
+				'id_msg' => $id_msg,
255
+				'id_member' => $this->memID,
256
+				'actor_is_admin' => $this->context['user']['is_admin'] ? 1 : 0
257
+			]);
258
+		if ($this->smcFunc['db_num_rows']($request) == 1)
259
+		{
260
+			$row = $this->smcFunc['db_fetch_row']($request);
261
+			$row['poster_ip'] = inet_dtop($row['poster_ip']);
262
+		}
263
+		$this->smcFunc['db_free_result']($request);
264
+
265
+		return $row;
266
+	}
267
+
268
+	/**
269
+	 * Get data from a message we are tracking.
270
+	 *
271
+	 * @internal
272
+	 * @CalledIn SMF 2.0, SMF 2.1
273
+	 * @version 1.5.0
274
+	 * @since 1.5.0
275
+	 * @return ?array Poster data.
276
+	 */
277
+	private function TrackSFSSubmit(array $data): void
278
+	{
279
+		$post_data = http_build_query($data, '', '&');
280
+
281
+		// SMF 2.0 has the fetch_web_data in the Subs-Packages, 2.1 it is in Subs.php.
282
+		if ($this->SFSclass->versionCheck('2.0', 'smf'))
283
+			$this->SFSclass->loadSources('Subs-Package');
284
+
285
+		// Now we have a URL, lets go get it.
286
+		$result = fetch_web_data('https://www.stopforumspam.com/add', $post_data);
287
+
288
+		if (strpos($result, 'data submitted successfully') === false)
289
+			$this->context['submission_failed'] = $this->txt('sfs_submission_error');
290
+		elseif (isset($_POST['sfs_submitban']))
291
+			redirectexit($this->scripturl . '?action=admin;area=ban;sa=add;u=' . $this->memID);
292
+		else
293
+			$this->context['submission_success'] = $this->SFSclass->txt('sfs_submission_success');
294
+	}
295
+}
0 296
\ No newline at end of file
... ...
@@ -3,7 +3,7 @@
3 3
 <package-info xmlns="http://www.simplemachines.org/xml/package-info" xmlns:smf="http://www.simplemachines.org/">
4 4
 	<id>SleePy:StopForumSpam</id>
5 5
 	<name>Stop Forum Spam</name>
6
-	<version>1.4</version>
6
+	<version>1.5.0-Alpha1</version>
7 7
 	<type>modification</type>
8 8
 
9 9
 	<!-- 2.0 has no support for hooks -->
... ...
@@ -13,9 +13,8 @@
13 13
 		<code type="file">sfs_hooks_install.php</code>
14 14
 		<modification>install_smf20.xml</modification>
15 15
 
16
-		<require-file name="SFS.php" destination="$sourcedir" />
17
-		<require-file name="SFS-Subs-Admin.php" destination="$sourcedir" />
18
-		<require-file name="SFS-Subs-Logs.php" destination="$sourcedir" />
16
+		<require-file name="StopForumSpam.php" destination="$sourcedir" />
17
+		<require-dir name="StopForumSpam" destination="$sourcedir" />
19 18
 		<require-file name="StopForumSpam.template.php" destination="$themedir" />
20 19
 
21 20
 		<!-- this dir may not exist in SMF -->
... ...
@@ -41,9 +40,8 @@
41 40
 		<remove-dir name="$themes_dir/default/languages/StopForumSpam.finnish.php" />
42 41
 
43 42
 		<!-- source files, removed -->
44
-		<remove-file name="$sourcedir/SFS.php" />
45
-		<remove-file name="$sourcedir/SFS-Subs-Admin.php" />
46
-		<remove-file name="$sourcedir/SFS-Subs-Logs.php" />
43
+		<remove-file name="$sourcedir/StopForumSpam.php" />
44
+		<remove-dir name="$sourcedir/StopForumSpam" />
47 45
 		<remove-file name="$themedir/StopForumSpam.template.php" />
48 46
 		<remove-file name="$themedir/images/admin/sfs.webp" />
49 47
 	</uninstall>
... ...
@@ -52,9 +50,8 @@
52 50
 		<readme type="file" parsebbc="true">README.bbc</readme>
53 51
 		<database>install_sfs.php</database>
54 52
 
55
-		<require-file name="SFS.php" destination="$sourcedir" />
56
-		<require-file name="SFS-Subs-Admin.php" destination="$sourcedir" />
57
-		<require-file name="SFS-Subs-Logs.php" destination="$sourcedir" />
53
+		<require-file name="StopForumSpam.php" destination="$sourcedir" />
54
+		<require-dir name="StopForumSpam" destination="$sourcedir" />
58 55
 		<require-file name="StopForumSpam.template.php" destination="$themedir" />
59 56
 
60 57
 		<require-file name="language/StopForumSpam.english.php" destination="$themes_dir/default/languages" />
... ...
@@ -66,16 +63,16 @@
66 63
 
67 64
 		<!-- All the hooks -->
68 65
 			<!-- Main Section -->
69
-			<hook hook="integrate_pre_include" function="$sourcedir/SFS.php" />
66
+			<hook hook="integrate_pre_include" function="$sourcedir/StopForumSpam.php" />
70 67
 			<hook hook="integrate_pre_load" function="SFS::hook_pre_load" />
71 68
 			<hook hook="integrate_register" function="SFS::hook_register" />
72 69
 			<hook hook="integrate_create_control_verification_test" function="SFS::hook_create_control_verification_test" />
73 70
 
74 71
 			<!-- Admin Section -->
75
-			<hook hook="integrate_admin_include" function="$sourcedir/SFS-Subs-Admin.php" />
72
+			<hook hook="integrate_admin_include" function="$sourcedir/StopForumSpam/SFS-Admin.php" />
76 73
 			<hook hook="integrate_admin_areas" function="SFSA::hook_admin_areas" />
77 74
 			<hook hook="integrate_modify_modifications" function="SFSA::hook_modify_modifications" />
78
-			<hook hook="integrate_manage_logs" function="SFSA::hook_manage_logs" />
75
+			<hook hook="integrate_manage_logs" function="SFSL::hook_manage_logs" file="$sourcedir/StopForumSpam/SFS-Logs.php" />
79 76
 			<hook hook="integrate_manage_registrations" function="SFSA::hook_manage_registrations" />
80 77
 
81 78
 			<!-- Profile Section -->
... ...
@@ -94,7 +91,7 @@
94 91
 
95 92
 		<!-- All the hooks, removed -->
96 93
 			<!-- Main Section -->
97
-			<hook hook="integrate_pre_include" function="$sourcedir/SFS.php" reverse="true" />
94
+			<hook hook="integrate_pre_include" function="$sourcedir/StopForumSpam.php" reverse="true" />
98 95
 			<hook hook="integrate_pre_load" function="SFS::hook_pre_load" reverse="true" />
99 96
 			<hook hook="integrate_register" function="SFS::hook_register" reverse="true" />
100 97
 			<hook hook="integrate_create_control_verification_test" function="SFS::hook_create_control_verification_test" reverse="true" />
... ...
@@ -103,7 +100,7 @@
103 100
 			<hook hook="integrate_admin_include" function="$sourcedir/SFS-Subs-Admin.php" reverse="true" />
104 101
 			<hook hook="integrate_admin_areas" function="SFSA::hook_admin_areas" reverse="true" />
105 102
 			<hook hook="integrate_modify_modifications" function="SFSA::hook_modify_modifications" reverse="true" />
106
-			<hook hook="integrate_manage_logs" function="SFSA::hook_manage_logs" reverse="true" />
103
+			<hook hook="integrate_manage_logs" function="SFSL::hook_manage_logs" reverse="true" file="$sourcedir/SFS-Subs-Logs.php" />
107 104
 			<hook hook="integrate_manage_registrations" function="SFSA::hook_manage_registrations" reverse="true" />
108 105
 
109 106
 			<!-- Profile Section -->
... ...
@@ -118,20 +115,34 @@
118 115
 		<remove-file name="$themes_dir/default/languages/StopForumSpam.finnish.php" />
119 116
 
120 117
 		<!-- source files, removed -->
121
-		<remove-file name="$sourcedir/SFS.php" />
122
-		<remove-file name="$sourcedir/SFS-Subs-Admin.php" />
123
-		<remove-file name="$sourcedir/SFS-Subs-Logs.php" />
118
+		<remove-file name="$sourcedir/StopForumSpam.php" />
119
+		<remove-dir name="$sourcedir/StopForumSpam" />
124 120
 		<remove-file name="$themedir/StopForumSpam.template.php" />
125 121
 		<remove-file name="$themedir/images/admin/sfs.webp" />
126 122
 	</uninstall>
127 123
 
128
-	<upgrade from="1.0-1.3" for="2.1.*">
124
+	<upgrade from="1.0-1.4" for="2.1.*">
129 125
 		<require-file name="language/StopForumSpam.english.php" destination="$themes_dir/default/languages" />
130 126
         <require-file name="language/StopForumSpam.finnish.php" destination="$themes_dir/default/languages" />
127
+
128
+		<require-file name="StopForumSpam.php" destination="$sourcedir" />
129
+		<require-dir name="StopForumSpam" destination="$sourcedir" />
130
+
131
+		<remove-file name="$sourcedir/SFS.php" error="ignore" />
132
+		<remove-file name="$sourcedir/SFS-Subs-Admin.php" error="ignore" />
133
+		<remove-file name="$sourcedir/SFS-Subs-Logs.php" error="ignore" />
134
+
131 135
 		<require-file name="StopForumSpam.template.php" destination="$themedir" />
132
-		<require-file name="SFS.php" destination="$sourcedir" />
133
-		<require-file name="SFS-Subs-Admin.php" destination="$sourcedir" />
134
-        <require-file name="SFS-Subs-Logs.php" destination="$sourcedir" />
136
+
137
+		<hook hook="integrate_pre_include" function="$sourcedir/StopForumSpam.php" />
138
+		<hook hook="integrate_pre_include" function="$sourcedir/SFS.php" reverse="true" />
139
+
140
+		<hook hook="integrate_admin_include" function="$sourcedir/StopForumSpam/SFS-Admin.php" />
141
+		<hook hook="integrate_admin_include" function="$sourcedir/SFS-Subs-Admin.php" reverse="true" />
142
+
143
+		<hook hook="integrate_manage_logs" function="SFSA::hook_manage_logs" reverse="true" error="ignore" />
144
+		<hook hook="integrate_manage_logs" function="SFSL::hook_manage_logs" file="$sourcedir/StopForumSpam/SFS-Logs.php" />
145
+
135 146
 		<hook hook="integrate_pre_profile_areas" function="SFS::hook_pre_profile_areas" />
136 147
 		<hook hook="integrate_prepare_display_context" function="SFS::hook_prepare_display_context" />
137 148
 		<hook hook="integrate_mod_buttons" function="SFS::hook_mod_buttons" />
... ...
@@ -141,14 +152,20 @@
141 152
 		<require-file name="sfs.webp" destination="$themedir/images/admin" />
142 153
 	</upgrade>
143 154
 
144
-	<upgrade from="1.0-1.3" for="2.0.*">
155
+	<upgrade from="1.0-1.4" for="2.0.*">
145 156
 		<code type="file">sfs_hooks_install.php</code>
157
+
146 158
 		<require-file name="language/StopForumSpam.english.php" destination="$themes_dir/default/languages" />
147 159
         <require-file name="language/StopForumSpam.finnish.php" destination="$themes_dir/default/languages" />
160
+
161
+		<require-file name="StopForumSpam.php" destination="$sourcedir" />
162
+		<require-dir name="StopForumSpam" destination="$sourcedir" />
163
+
164
+		<remove-file name="$sourcedir/SFS.php" error="ignore" />
165
+		<remove-file name="$sourcedir/SFS-Subs-Admin.php" error="ignore" />
166
+		<remove-file name="$sourcedir/SFS-Subs-Logs.php" error="ignore" />
167
+
148 168
 		<require-file name="StopForumSpam.template.php" destination="$themedir" />
149
-		<require-file name="SFS.php" destination="$sourcedir" />
150
-		<require-file name="SFS-Subs-Admin.php" destination="$sourcedir" />
151
-        <require-file name="SFS-Subs-Logs.php" destination="$sourcedir" />
152 169
 
153 170
 		<!-- this dir may not exist in SMF -->
154 171
 		<create-dir name="admin" destination="$themedir/images" />
... ...
@@ -3,7 +3,7 @@
3 3
  * The Main class for Stop Forum Spam
4 4
  * @package StopForumSpam
5 5
  * @author SleePy <sleepy @ simplemachines (dot) org>
6
- * @copyright 2019
6
+ * @copyright 2023
7 7
  * @license 3-Clause BSD https://opensource.org/licenses/BSD-3-Clause
8 8
  */
9 9
 
... ...
@@ -18,21 +18,30 @@ elseif (!defined('SMF')) // If we are outside SMF and can't find SSI.php, then t
18 18
 if (SMF == 'SSI')
19 19
 	db_extend('packages');
20 20
 
21
-$hooks = array(
21
+$hooks = [
22 22
 	// Main sections.
23
-	'integrate_pre_include' => '$sourcedir/SFS.php',
23
+	'integrate_pre_include' => '$sourcedir/StopForumSpam.php',
24 24
 	'integrate_pre_load' => 'SFS::hook_pre_load',
25 25
 	'integrate_register' => 'SFS::hook_register',
26 26
 
27 27
 	// Admin Sections.
28
-	'integrate_admin_include' => '$sourcedir/SFS-Subs-Admin.php',
28
+	'integrate_admin_include' => '$sourcedir/StopForumSpam/SFS-Admin.php',
29 29
 	'integrate_admin_areas' => 'SFSA::hook_admin_areas',
30 30
 	'integrate_modify_modifications' => 'SFSA::hook_modify_modifications',
31
-	'integrate_manage_logs' => 'SFSA::hook_manage_logs',
31
+	'integrate_manage_logs' => 'SFSL::hook_manage_logs',
32 32
 
33 33
 	// Profile Section.
34
-	'integrate_profile_areas' => 'SFS::hook_pre_profile_areas'
35
-);
36
-
34
+	'integrate_profile_areas' => 'SFSP::hook_pre_profile_areas'
35
+];
37 36
 foreach ($hooks as $hook => $func)
38 37
 	add_integration_function($hook, $func, true);
38
+
39
+// Remove old hooks.
40
+$removeHooks = [
41
+	['integrate_manage_logs','SFSA::hook_manage_logs'],
42
+	['integrate_pre_include','$sourcedir/SFS.php'],
43
+	['integrate_admin_include','$sourcedir/SFS-Subs-Admin.php'],
44
+	['integrate_profile_areas','SFS::hook_pre_profile_areas']
45
+];
46
+foreach ($removeHooks as $remove)
47
+	remove_integration_function($remove[0], $remove[1]);
39 48
\ No newline at end of file
... ...
@@ -3,7 +3,7 @@
3 3
  * The Main class for Stop Forum Spam
4 4
  * @package StopForumSpam
5 5
  * @author SleePy <sleepy @ simplemachines (dot) org>
6
- * @copyright 2019
6
+ * @copyright 2023
7 7
  * @license 3-Clause BSD https://opensource.org/licenses/BSD-3-Clause
8 8
  */
9 9
 
... ...
@@ -18,21 +18,21 @@ elseif (!defined('SMF')) // If we are outside SMF and can't find SSI.php, then t
18 18
 if (SMF == 'SSI')
19 19
 	db_extend('packages');
20 20
 
21
-$hooks = array(
21
+$hooks = [
22 22
 	// Main sections.
23
-	'integrate_pre_include' => '$sourcedir/SFS.php',
23
+	'integrate_pre_include' => '$sourcedir/StopForumSpam.php',
24 24
 	'integrate_pre_load' => 'SFS::hook_pre_load',
25 25
 	'integrate_register' => 'SFS::hook_register',
26 26
 
27 27
 	// Admin Sections.
28
-	'integrate_admin_include' => '$sourcedir/SFS-Subs-Admin.php',
28
+	'integrate_admin_include' => '$sourcedir/StopForumSpam/SFS-Admin.php',
29 29
 	'integrate_admin_areas' => 'SFSA::hook_admin_areas',
30 30
 	'integrate_modify_modifications' => 'SFSA::hook_modify_modifications',
31
-	'integrate_manage_logs' => 'SFSA::hook_manage_logs',
31
+	'integrate_manage_logs' => 'SFSL::hook_manage_logs',
32 32
 
33 33
 	// Profile Section.
34
-	'integrate_profile_areas' => 'SFS::hook_pre_profile_areas'
35
-);
34
+	'integrate_profile_areas' => 'SFSP::hook_pre_profile_areas'
35
+];
36 36
 
37 37
 foreach ($hooks as $hook => $func)
38 38
 	remove_integration_function($hook, $func);
39 39
\ No newline at end of file
40 40