Refactoring code
jdarwood007

jdarwood007 commited on 2020-01-04 11:24:32
Showing 7 changed files, with 1229 additions and 166 deletions.

... ...
@@ -53,10 +53,15 @@ class SFSA
53 53
 		global $smcFunc;
54 54
 
55 55
 		if (is_null(self::$SFSAclass))
56
+		{
57
+			if (!empty($smcFunc['SFSA']))
58
+				self::$SFSAclass = $smcFunc['SFSA'];
59
+			else
56 60
 			{
57 61
 				self::$SFSAclass = new SFSA();
58 62
 				$smcFunc['SFSA'] = self::$SFSAclass;
59 63
 			}
64
+		}
60 65
 
61 66
 		return self::$SFSAclass;
62 67
 	}
... ...
@@ -0,0 +1,931 @@
1
+<?php
2
+
3
+/**
4
+ * The Logs 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 SFSL
12
+{
13
+	public static $SFSLclass = null;
14
+	private $SFSclass = null;
15
+	private $SFSAclass = null;
16
+
17
+	/**
18
+	 * @var string URLS we need to SFS for UI presentation.
19
+	 */
20
+	private $urlSFSipCheck = 'https://www.stopforumspam.com/ipcheck/%1$s';
21
+	private $urlSFSsearch = 'https://www.stopforumspam.com/search/%1$s';
22
+
23
+	/**
24
+	 * @var mixed Search area handling.
25
+	 */
26
+	private $search_types = array();
27
+	private $search_params = array();
28
+	private $search_params_column = '';
29
+	private $search_params_string = null;
30
+	private $search_params_type = null;
31
+	private $canDeleteLogs = false;
32
+	private $logSearch = array();
33
+
34
+	/**
35
+	 * @var int How long we disable removing logs.
36
+	 */
37
+	private $hoursDisabled = 24;
38
+
39
+	/**
40
+	 * Creates a self reference to the SFS Log class for use later.
41
+	 *
42
+	 * @version 1.2
43
+	 * @since 1.2
44
+	 * @return object The SFS Log class is returned.
45
+	 */
46
+	public static function selfClass()
47
+	{
48
+		global $smcFunc;
49
+
50
+		if (is_null(self::$SFSLclass))
51
+		{
52
+			if (!empty($smcFunc['SFSL']))
53
+				self::$SFSLclass = $smcFunc['SFSL'];
54
+			else
55
+			{
56
+				self::$SFSLclass = new SFSL();
57
+				$smcFunc['SFSL'] = self::$SFSLclass;
58
+			}
59
+		}
60
+
61
+		return self::$SFSLclass;
62
+	}
63
+
64
+	/**
65
+	 * Build the class, figure out what software/version we have.
66
+	 * Loads up the defaults.
67
+	 *
68
+	 * @CalledIn SMF 2.0, SMF 2.1
69
+	 * @version 1.2
70
+	 * @since 1.2
71
+	 * @return void No return is generated
72
+	 */
73
+	public function __construct()
74
+	{
75
+		global $smcFunc;
76
+	
77
+		$this->SFSclass = &$smcFunc['classSFS'];
78
+		$this->SFSAclass = &SFSA::selfClass();
79
+	}
80
+
81
+	/**
82
+	 * In some software/versions, we can hook into the logs section.
83
+	 * In others we hook into the modifications settings.
84
+	 *
85
+	 * @param array $log_functions All possible log functions.
86
+	 *
87
+	 * @api
88
+	 * @CalledIn SMF 2.1
89
+	 * @See SFSA::startupLogs
90
+	 * @version 1.2
91
+	 * @since 1.0
92
+	 * @uses integrate_manage_logs - Hook SMF2.1
93
+	 * @return void No return is generated
94
+	 */
95
+	public static function hook_manage_logs(array &$log_functions): bool
96
+	{
97
+		// Add our logs sub action.
98
+		$log_functions['sfslog'] = array('SFS-Subs-Logs.php', 'startupLogs');
99
+
100
+		return self::selfClass()->AddToLogMenu($log_functions);
101
+	}
102
+
103
+	/**
104
+	 * Add the SFS logs to the log menu.
105
+	 *
106
+	 * @param array $log_functions All possible log functions.
107
+	 *
108
+	 * @CalledIn SMF 2.1
109
+	 * @See SFSA::startupLogs
110
+	 * @version 1.1
111
+	 * @since 1.1
112
+	 * @return void No return is generated
113
+	 */
114
+	public function AddToLogMenu(array &$log_functions): bool
115
+	{
116
+		global $context;
117
+
118
+		$context[$context['admin_menu_name']]['tab_data']['tabs']['sfslog'] = array(
119
+			'description' => $this->SFSclass->txt('sfs_admin_logs'),
120
+		);
121
+
122
+		return true;
123
+	}
124
+
125
+	/**
126
+	 * Log startup caller.
127
+	 * This has a $return_config just for simply complying with properly for searching the admin panel.
128
+	 *
129
+	 * @param bool $return_config If true, returns empty array to prevent breaking old SMF installs.
130
+	 *
131
+	 * @api
132
+	 * @CalledIn SMF 2.1
133
+	 * @See SFSA::loadLogs
134
+	 * @version 1.2
135
+	 * @since 1.0
136
+	 * @uses hook_manage_logs - Hook SMF2.1
137
+	 * @uses setupModifyModifications - Injected SMF2.0
138
+	 * @return void No return is generated
139
+	 */
140
+	public static function startupLogs(bool $return_config = false): array
141
+	{
142
+		return self::selfClass()->loadLogs();
143
+	}
144
+
145
+	/**
146
+	 * Actually show the logs.
147
+	 * This has a $return_config just for simply complying with properly for searching the admin panel.
148
+	 *
149
+	 * @param bool $return_config If true, returns empty array to prevent breaking old SMF installs.
150
+	 *
151
+	 * @api
152
+	 * @CalledIn SMF2.0, SMF 2.1
153
+	 * @See SFSA::getSFSLogEntries
154
+	 * @See SFSA::getSFSLogEntriesCount
155
+	 * @version 1.2
156
+	 * @since 1.0
157
+	 * @uses hook_manage_logs - Hook SMF2.1
158
+	 * @uses setupModifyModifications - Injected SMF2.0
159
+	 * @return void No return is generated
160
+	 */
161
+	public function loadLogs(bool $return_config = false): array
162
+	{
163
+		global $context, $smcFunc, $sourcedir;
164
+
165
+		// No Configs.
166
+		if ($return_config)
167
+			return array();
168
+
169
+		loadLanguage('Modlog');
170
+
171
+		$context['form_url'] = $this->SFSA->get('adminLogURL');
172
+		$context['log_url'] = $this->SFSA->get('adminLogURL');
173
+		$context['page_title'] = $this->SFSclass->txt('sfs_admin_logs');
174
+		$this->canDeleteLogs = allowedTo('admin_forum');
175
+
176
+		// Remove all..
177
+		if ((isset($_POST['removeall']) || isset($_POST['delete'])) && $this->canDeleteLogs)
178
+			$this->handleLogDeletes();
179
+
180
+		$sort_types = $this->handleLogsGetSortTypes();
181
+
182
+		$context['order'] = isset($_REQUEST['sort']) && isset($sort_types[$_REQUEST['sort']]) ? $_REQUEST['sort'] : 'time';
183
+
184
+		// Handle searches.
185
+		$this->handleLogSearch($context['log_url']);
186
+
187
+		require_once($sourcedir . '/Subs-List.php');
188
+
189
+		$listOptions = array(
190
+			'id' => 'sfslog_list',
191
+			'title' => $this->SFSclass->txt('sfs_admin_logs'),
192
+			'width' => '100%',
193
+			'items_per_page' => '50',
194
+			'no_items_label' => $this->SFSclass->txt('sfs_log_no_entries_found'),
195
+			'base_href' => $context['log_url'],
196
+			'default_sort_col' => 'time',
197
+			'get_items' => $this->loadLogsGetItems(),
198
+			'get_count' => $this->loadLogsGetCount(),
199
+			// This assumes we are viewing by user.
200
+			'columns' => array(
201
+				'type' => $this->loadLogsColumnType(),
202
+				'time' => $this->loadLogsColumnTime(),
203
+				'url' => $this->loadLogsColumnURL(),
204
+				'member' => $this->loadLogsColumnMember(),
205
+				'username' => $this->loadLogsColumnUsername(),
206
+				'email' => $this->loadLogsColumnEmail(),
207
+				'ip' => $this->loadLogsColumnIP(),
208
+				'ip2' => $this->loadLogsColumnIP(true),
209
+				'checks' => $this->loadLogsColumnChecks(),
210
+				'result' => $this->loadLogsColumnResult(),
211
+				'delete' => $this->loadLogsColumnDelete(),
212
+			),
213
+			'form' => array(
214
+				'href' => $context['form_url'],
215
+				'include_sort' => true,
216
+				'include_start' => true,
217
+				'hidden_fields' => array(
218
+					$context['session_var'] => $context['session_id'],
219
+					'params' => $this->search_params
220
+				),
221
+			),
222
+			'additional_rows' => array(
223
+				$this->loadLogsGetAddtionalRow(),
224
+			),
225
+		);
226
+
227
+		// Create the watched user list.
228
+		createList($listOptions);
229
+
230
+		$context['sub_template'] = 'show_list';
231
+		$context['default_list'] = 'sfslog_list';
232
+
233
+		return array();
234
+	}
235
+
236
+	/**
237
+	 * Handle when we want to delete a log and what to do.
238
+	 *
239
+	 * @internal
240
+	 * @CalledIn SMF2.0, SMF 2.1
241
+	 * @version 1.1
242
+	 * @since 1.1
243
+	 * @return void Nothing is returned, the logs are deleted as requested and admin redirected.
244
+	 */
245
+	private function handleLogDeletes(): void
246
+	{
247
+		if (isset($_POST['removeall']) && $this->canDeleteLogs)
248
+			$this->removeAllLogs();
249
+		elseif (!empty($_POST['remove']) && isset($_POST['delete']) && $this->canDeleteLogs)
250
+			$this->removeLogs(array_unique($_POST['delete']));
251
+	}
252
+
253
+	/**
254
+	 * loadLogs - Sort Types.
255
+	 *
256
+	 * @internal
257
+	 * @CalledIn SMF 2.0, SMF 2.1
258
+	 * @version 1.2
259
+	 * @since 1.2
260
+	 * @return array The valid Sort Types.
261
+	 */
262
+	private function handleLogsGetSortTypes(): array
263
+	{
264
+		return array(
265
+			'id_type' =>'l.id_type',
266
+			'log_time' => 'l.log_time',
267
+			'url' => 'l.url',
268
+			'member' => 'mem.id_member',
269
+			'username' => 'l.username',
270
+			'email' => 'l.email',
271
+			'ip' => 'l.ip',
272
+			'ip2' => 'l.ip2',
273
+		);
274
+	}
275
+
276
+	/**
277
+	 * loadLogs - Get Items.
278
+	 *
279
+	 * @internal
280
+	 * @CalledIn SMF2.0, SMF 2.1
281
+	 * @version 1.1
282
+	 * @since 1.1
283
+	 * @return array The options for the get_items
284
+	 */
285
+	private function loadLogsGetItems(): array
286
+	{
287
+		return array(
288
+			'function' => array($this, 'getSFSLogEntries'),
289
+			'params' => array(
290
+				(!empty($this->logSearch['string']) ? ' INSTR({raw:sql_type}, {string:search_string})' : ''),
291
+				array('sql_type' => $this->search_params_column, 'search_string' => $this->logSearch['string']),
292
+			),
293
+		);
294
+	}
295
+
296
+	/**
297
+	 * loadLogs - Get Count.
298
+	 *
299
+	 * @internal
300
+	 * @CalledIn SMF2.0, SMF 2.1
301
+	 * @version 1.1
302
+	 * @since 1.1
303
+	 * @return array The options for the get_items
304
+	 */
305
+	private function loadLogsGetCount(): array
306
+	{
307
+		return array(
308
+			'function' => array($this, 'getSFSLogEntriesCount'),
309
+			'params' => array(
310
+				(!empty($this->logSearch['string']) ? ' INSTR({raw:sql_type}, {string:search_string})' : ''),
311
+				array('sql_type' => $this->search_params_column, 'search_string' => $this->logSearch['string']),
312
+			),
313
+		);
314
+	}
315
+
316
+	/**
317
+	 * loadLogs - Load an additional row, for mostly deleting stuff.
318
+	 *
319
+	 * @internal
320
+	 * @CalledIn SMF2.0, SMF 2.1
321
+	 * @version 1.1
322
+	 * @since 1.1
323
+	 * @return array The options for the get_items
324
+	 */
325
+	private function loadLogsGetAddtionalRow(): array
326
+	{
327
+		global $smcFunc;
328
+
329
+		return array(
330
+			'position' => 'below_table_data',
331
+			'value' => '
332
+				' . $this->SFSclass->txt('sfs_log_search') . ' (' . $this->logSearch['label'] . '):
333
+				<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" />
334
+				' . ($this->canDeleteLogs ? ' |
335
+					<input type="submit" name="remove" value="' . $this->SFSclass->txt('modlog_remove') . '" class="button_submit" />
336
+					<input type="submit" name="removeall" value="' . $this->SFSclass->txt('modlog_removeall') . '" class="button_submit" />' : ''),
337
+		);
338
+	}
339
+
340
+
341
+	/**
342
+	 * loadLogs - Column - Type.
343
+	 *
344
+	 * @internal
345
+	 * @CalledIn SMF2.0, SMF 2.1
346
+	 * @version 1.1
347
+	 * @since 1.1
348
+	 * @return array The options for the column
349
+	 */
350
+	private function loadLogsColumnType(): array
351
+	{
352
+		return array(
353
+			'header' => array(
354
+				'value' => $this->SFSclass->txt('sfs_log_header_type'),
355
+				'class' => 'lefttext',
356
+			),
357
+			'data' => array(
358
+				'db' => 'type',
359
+				'class' => 'smalltext',
360
+			),
361
+			'sort' => array(
362
+			),
363
+		);
364
+	}
365
+
366
+	/**
367
+	 * loadLogs - Column - Time.
368
+	 *
369
+	 * @internal
370
+	 * @CalledIn SMF2.0, SMF 2.1
371
+	 * @version 1.1
372
+	 * @since 1.1
373
+	 * @return array The options for the column
374
+	 */
375
+	private function loadLogsColumnTime(): array
376
+	{
377
+		return array(
378
+			'header' => array(
379
+				'value' => $this->SFSclass->txt('sfs_log_header_time'),
380
+				'class' => 'lefttext',
381
+			),
382
+			'data' => array(
383
+				'db' => 'time',
384
+				'class' => 'smalltext',
385
+			),
386
+			'sort' => array(
387
+				'default' => 'l.log_time DESC',
388
+				'reverse' => 'l.log_time',
389
+			),
390
+		);
391
+	}
392
+
393
+	/**
394
+	 * loadLogs - Column - URL.
395
+	 *
396
+	 * @internal
397
+	 * @CalledIn SMF2.0, SMF 2.1
398
+	 * @version 1.1
399
+	 * @since 1.1
400
+	 * @return array The options for the column
401
+	 */
402
+	private function loadLogsColumnURL(): array
403
+	{
404
+		return array(
405
+			'header' => array(
406
+				'value' => $this->SFSclass->txt('sfs_log_header_url'),
407
+				'class' => 'lefttext',
408
+			),
409
+			'data' => array(
410
+				'db' => 'url',
411
+				'class' => 'smalltext',
412
+				'style' => 'word-break: break-word;',
413
+			),
414
+			'sort' => array(
415
+				'default' => 'l.url DESC',
416
+				'reverse' => 'l.url',
417
+			),
418
+		);
419
+	}
420
+
421
+	/**
422
+	 * loadLogs - Column - Member.
423
+	 *
424
+	 * @internal
425
+	 * @CalledIn SMF2.0, SMF 2.1
426
+	 * @version 1.1
427
+	 * @since 1.1
428
+	 * @return array The options for the column
429
+	 */
430
+	private function loadLogsColumnMember(): array
431
+	{
432
+		return array(
433
+			'header' => array(
434
+				'value' => $this->SFSclass->txt('sfs_log_header_member'),
435
+				'class' => 'lefttext',
436
+			),
437
+			'data' => array(
438
+				'db' => 'member_link',
439
+				'class' => 'smalltext',
440
+			),
441
+			'sort' => array(
442
+				'default' => 'mem.id_member',
443
+				'reverse' => 'mem.id_member DESC',
444
+			),
445
+		);
446
+	}
447
+
448
+	/**
449
+	 * loadLogs - Column - Username.
450
+	 *
451
+	 * @internal
452
+	 * @CalledIn SMF2.0, SMF 2.1
453
+	 * @version 1.1
454
+	 * @since 1.1
455
+	 * @return array The options for the column
456
+	 */
457
+	private function loadLogsColumnUsername(): array
458
+	{
459
+		return array(
460
+			'header' => array(
461
+				'value' => $this->SFSclass->txt('sfs_log_header_username'),
462
+				'class' => 'lefttext',
463
+			),
464
+			'data' => array(
465
+				'db' => 'username',
466
+				'class' => 'smalltext',
467
+			),
468
+			'sort' => array(
469
+				'default' => 'l.username',
470
+				'reverse' => 'l.username DESC',
471
+			),
472
+		);
473
+	}
474
+
475
+	/**
476
+	 * loadLogs - Column - Email.
477
+	 *
478
+	 * @internal
479
+	 * @CalledIn SMF2.0, SMF 2.1
480
+	 * @version 1.1
481
+	 * @since 1.1
482
+	 * @return array The options for the column
483
+	 */
484
+	private function loadLogsColumnEmail(): array
485
+	{
486
+		return array(
487
+			'header' => array(
488
+				'value' => $this->SFSclass->txt('sfs_log_header_email'),
489
+				'class' => 'lefttext',
490
+			),
491
+			'data' => array(
492
+				'db' => 'email',
493
+				'class' => 'smalltext',
494
+			),
495
+			'sort' => array(
496
+				'default' => 'l.email',
497
+				'reverse' => 'l.email DESC',
498
+			),
499
+		);
500
+	}
501
+
502
+	/**
503
+	 * loadLogs - Column - IP.
504
+	 *
505
+	 * @param string $ip2 If true, use ip2
506
+	 * @internal
507
+	 * @CalledIn SMF2.0, SMF 2.1
508
+	 * @version 1.1
509
+	 * @since 1.1
510
+	 * @return array The options for the column
511
+	 */
512
+	private function loadLogsColumnIP(bool $ip2 = false): array
513
+	{
514
+		return array(
515
+			'header' => array(
516
+				'value' => $this->SFSclass->txt('sfs_log_header_ip' . ($ip2 ? '2' : '')),
517
+				'class' => 'lefttext',
518
+			),
519
+			'data' => array(
520
+				'db' => 'ip' . ($ip2 ? '2' : ''),
521
+				'class' => 'smalltext',
522
+			),
523
+			'sort' => array(
524
+				'default' => 'l.ip' . ($ip2 ? '2' : ''),
525
+				'reverse' => 'l.ip' . ($ip2 ? '2' : '') . ' DESC',
526
+			),
527
+		);
528
+	}
529
+
530
+	/**
531
+	 * loadLogs - Column - Checks.
532
+	 *
533
+	 * @internal
534
+	 * @CalledIn SMF2.0, SMF 2.1
535
+	 * @version 1.1
536
+	 * @since 1.1
537
+	 * @return array The options for the column
538
+	 */
539
+	private function loadLogsColumnChecks(): array
540
+	{
541
+		return array(
542
+			'header' => array(
543
+				'value' => $this->SFSclass->txt('sfs_log_checks'),
544
+				'class' => 'lefttext',
545
+			),
546
+			'data' => array(
547
+				'db' => 'checks',
548
+				'class' => 'smalltext',
549
+				'style' => 'word-break: break-word;',
550
+			),
551
+			'sort' => array(),
552
+		);
553
+	}
554
+
555
+	/**
556
+	 * loadLogs - Column - Result.
557
+	 *
558
+	 * @internal
559
+	 * @CalledIn SMF2.0, SMF 2.1
560
+	 * @version 1.1
561
+	 * @since 1.1
562
+	 * @return array The options for the column
563
+	 */
564
+	private function loadLogsColumnResult(): array
565
+	{
566
+		return array(
567
+			'header' => array(
568
+				'value' => $this->SFSclass->txt('sfs_log_result'),
569
+				'class' => 'lefttext',
570
+			),
571
+			'data' => array(
572
+				'db' => 'result',
573
+				'class' => 'smalltext',
574
+				'style' => 'word-break: break-word;',
575
+			),
576
+			'sort' => array(),
577
+		);
578
+	}
579
+
580
+	/**
581
+	 * loadLogs - Column - Delete.
582
+	 *
583
+	 * @internal
584
+	 * @CalledIn SMF2.0, SMF 2.1
585
+	 * @version 1.1
586
+	 * @since 1.1
587
+	 * @return array The options for the column
588
+	 */
589
+	private function loadLogsColumnDelete(): array
590
+	{
591
+		return array(
592
+			'header' => array(
593
+				'value' => '<input type="checkbox" name="all" class="input_check" onclick="invertAll(this, this.form);" />',
594
+			),
595
+			'data' => array(
596
+				'function' => function($entry)
597
+				{
598
+					return '<input type="checkbox" class="input_check" name="delete[]" value="' . $entry['id'] . '"' . ($entry['editable'] ? '' : ' disabled="disabled"') . ' />';
599
+				},
600
+				'style' => 'text-align: center;',
601
+			),
602
+		);
603
+	}
604
+
605
+	/**
606
+	 * Get the log data and returns it ready to go for GenericList handling.
607
+	 *
608
+	 * @param int $start The index for where we offset or start at for the list
609
+	 * @param int $items_per_page How many items we are going to show on this page.
610
+	 * @param string $sort The column we are sorting by.
611
+	 * @param string $query_string The search string we are using to filter log data.
612
+	 * @param array $query_params Extra parameters for searching.
613
+	 *
614
+	 * @api
615
+	 * @CalledIn SMF 2.0, SMF 2.1
616
+	 * @See SFSA::loadLogs
617
+	 * @version 1.2
618
+	 * @since 1.0
619
+	 * @uses hook_manage_logs - Hook SMF2.1
620
+	 * @uses setupModifyModifications - Injected SMF2.0
621
+	 * @return void No return is generated
622
+	 */
623
+	public function getSFSLogEntries(int $start, int $items_per_page, string $sort, string $query_string = '', array $query_params = array()): array
624
+	{
625
+		global $scripturl, $context, $smcFunc;
626
+
627
+		// Fetch all of our logs.
628
+		$result = $smcFunc['db_query']('', '
629
+			SELECT
630
+				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,
631
+				mem.real_name, mg.group_name
632
+			FROM {db_prefix}log_sfs AS l
633
+				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = l.id_member)
634
+				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)
635
+				WHERE id_type IS NOT NULL'
636
+				. (!empty($query_string) ? '
637
+					AND ' . $query_string : '') . '
638
+			ORDER BY ' . $sort . '
639
+			LIMIT {int:start}, {int:items_per_page}',
640
+			array_merge($query_params, array(
641
+				'start' => $start,
642
+				'items_per_page' => $items_per_page,
643
+				'reg_group_id' => 0,
644
+			))
645
+		);
646
+
647
+		$entries = array();
648
+		while ($row = $smcFunc['db_fetch_assoc']($result))
649
+			$entries[$row['id_sfs']] => $this->getSFSLogPrepareEntry($row);			
650
+		$smcFunc['db_free_result']($result);
651
+
652
+		return $entries;
653
+	}
654
+
655
+	/**
656
+	 * Formats a log entry for display.
657
+	 *
658
+	 * @param array $row The raw row data.
659
+	 *
660
+	 * @api
661
+	 * @CalledIn SMF 2.0, SMF 2.1
662
+	 * @See SFSA::getSFSLogEntries
663
+	 * @version 1.2
664
+	 * @since 1.2
665
+	 * @return array An array of data ready to be sent to output
666
+	 */
667
+	public function getSFSLogPrepareEntry(array $row = array()): array
668
+	{
669
+		$return = array(
670
+			'id' => $row['id_sfs'],
671
+			'type' => $this->SFSclass->txt('sfs_log_types_' . $row['id_type']),
672
+			'time' => timeformat($row['log_time']),
673
+			'url' => preg_replace('~http(s)?://~i', 'hxxp\\1://', $row['url']),
674
+			'timestamp' => forum_time(true, $row['log_time']),
675
+			'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']),
676
+			'username' => $row['username'],
677
+			'email' => $row['email'],
678
+			'ip' => '<a href="' . sprintf($this->urlSFSipCheck, $row['ip']) . '">' . $row['ip'] . '</a>',
679
+			'ip2' => '<a href="' . sprintf($this->urlSFSipCheck, $row['ip2']) . '">' . $row['ip2'] . '</a>',
680
+			'editable' => true, //time() > $row['log_time'] + $this->hoursDisabled * 3600,
681
+			'checks_raw' => $row['checks'],
682
+			'result_raw' => $row['result'],
683
+		);
684
+
685
+		$checksDecoded = $this->SFSclass->decodeJSON($row['checks']);
686
+
687
+		// If we know what check triggered this, link it up to be searched.
688
+		if ($row['id_type'] == 1)
689
+			$return['checks'] = '<a href="' . sprintf($this->urlSFSsearch, $checksDecoded['value']) . '">' . $checksDecoded['value'] . '</a>';
690
+		elseif ($row['id_type'] == 2)
691
+			$return['checks'] = '<a href="' . sprintf($this->urlSFSsearch, $checksDecoded['value']) . '">' . $checksDecoded['value'] . '</a>';
692
+		elseif ($row['id_type'] == 3)
693
+			$return['checks'] = '<a href="' . sprintf($this->urlSFSsearch, $checksDecoded['value']) . '">' . $checksDecoded['value'] . '</a>';
694
+		// No idea what triggered it, parse it out cleanly.  Could be debug data as well.
695
+		else
696
+		{
697
+			$return['checks'] = '';
698
+
699
+			foreach ($checksDecoded as $ckey => $vkey)
700
+				foreach ($vkey as $key => $value)
701
+					$return['checks'] .= ucfirst($key) . ':' . $value . '<br>';					
702
+		}
703
+
704
+		// This tells us what it matched on exactly.
705
+		if (strpos($row['result'], ',') !== false)
706
+		{
707
+			list($resultType, $resultMatch, $extra) = explode(',', $row['result'] . ',,,');
708
+			$return['result'] = sprintf($this->SFSclass->txt('sfs_log_matched_on'), $resultType, $resultMatch);
709
+
710
+			// If this was a IP ban, note it.
711
+			if ($resultType == 'ip' && !empty($extra))
712
+				$return['result'] .= ' ' . $this->SFSclass->txt('sfs_log_auto_banned');			
713
+			if ($resultType == 'username' && !empty($extra))
714
+				$return['result'] .= ' ' . sprintf($this->SFSclass->txt('sfs_log_confidence'), $extra);			
715
+		}
716
+		else
717
+			$return['result'] = $row['result'];
718
+
719
+		return $return;
720
+	}
721
+
722
+	/**
723
+	 * Get the log counts and returns it ready to go for GenericList handling.
724
+	 *
725
+	 * @param string $query_string The search string we are using to filter log data.
726
+	 * @param array $query_params Extra parameters for searching.
727
+	 *
728
+	 * @api
729
+	 * @CalledIn SMF 2.0, SMF 2.1
730
+	 * @See SFSA::loadLogs
731
+	 * @version 1.0
732
+	 * @since 1.0
733
+	 * @uses hook_manage_logs - Hook SMF2.1
734
+	 * @uses setupModifyModifications - Injected SMF2.0
735
+	 * @return void No return is generated
736
+	 */
737
+	public function getSFSLogEntriesCount(string $query_string = '', array $query_params = array()): int
738
+	{
739
+		global $smcFunc, $user_info;
740
+
741
+		$result = $smcFunc['db_query']('', '
742
+			SELECT COUNT(*)
743
+			FROM {db_prefix}log_sfs AS l
744
+				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = l.id_member)
745
+				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)
746
+				WHERE id_type IS NOT NULL'
747
+				. (!empty($query_string) ? '
748
+					AND ' . $query_string : ''),
749
+			array_merge($query_params, array(
750
+				'reg_group_id' => 0,
751
+			))
752
+		);
753
+		list ($entry_count) = $smcFunc['db_fetch_row']($result);
754
+		$smcFunc['db_free_result']($result);
755
+
756
+		return (int) $entry_count;
757
+	}
758
+
759
+	/**
760
+	 * Remove all logs, except those less than 24 hours old.
761
+	 *
762
+	 * @api
763
+	 * @CalledIn SMF 2.0, SMF 2.1
764
+	 * @See SFSA::loadLogs
765
+	 * @version 1.0
766
+	 * @since 1.0
767
+	 * @return void No return is generated
768
+	 */
769
+	private function removeAllLogs(): void
770
+	{
771
+		global $smcFunc;
772
+
773
+		checkSession();
774
+
775
+		$smcFunc['db_query']('', '
776
+			DELETE FROM {db_prefix}log_sfs
777
+			WHERE log_time < {int:twenty_four_hours_wait}',
778
+			array(
779
+				'twenty_four_hours_wait' => time() - $this->hoursDisabled * 3600,
780
+			)
781
+		);
782
+	}
783
+
784
+	/**
785
+	 * Remove specific logs, except those less than 24 hours old.
786
+	 *
787
+	 * @param array $entries A array of the ids that we want to remove.
788
+	 *
789
+	 * @api
790
+	 * @CalledIn SMF 2.0, SMF 2.1
791
+	 * @See SFSA::loadLogs
792
+	 * @version 1.0
793
+	 * @since 1.0
794
+	 * @return void No return is generated
795
+	 */
796
+	private function removeLogs(array $entries): void
797
+	{
798
+		global $smcFunc;
799
+
800
+		checkSession();
801
+
802
+		$smcFunc['db_query']('', '
803
+			DELETE FROM {db_prefix}log_sfs
804
+			WHERE id_sfs IN ({array_string:delete_actions})
805
+				AND log_time < {int:twenty_four_hours_wait}',
806
+			array(
807
+				'twenty_four_hours_wait' => time() - $this->hoursDisabled * 3600,
808
+				'delete_actions' => $entries,
809
+			)
810
+		);
811
+	}
812
+
813
+	/**
814
+	 * Handle searching for logs.
815
+	 *
816
+	 * @param string $url The base_href
817
+	 * @internal
818
+	 * @CalledIn SMF 2.0, SMF 2.1
819
+	 * @version 1.0
820
+	 * @since 1.0
821
+	 * @return void No return is generated here.
822
+	 */
823
+	private function handleLogSearch(string &$url): void
824
+	{
825
+		global $context, $txt;
826
+
827
+		// If we have some data from a search, lets bring it back out.
828
+		$this->search_params = $this->handleLogSearchParams();
829
+
830
+		// What we can search.
831
+		$this->search_types = $this->handleLogSearchTypes();
832
+		$this->search_params_string = $this->handleLogSearchParamsString();
833
+		$this->search_params_type = $this->handleLogSearchParamsType();
834
+
835
+		$this->search_params_column = $this->search_types[$this->search_params_type]['sql'];
836
+
837
+		// Setup the search context.
838
+		$this->search_params = empty($this->search_params_string) ? '' : base64_encode(json_encode(array(
839
+			'string' => $this->search_params_string,
840
+			'type' => $this->search_params_type,
841
+		)));
842
+		$this->logSearch = array(
843
+			'string' => $this->search_params_string,
844
+			'type' => $this->search_params_type,
845
+			'label' => $this->search_types[$this->search_params_type]['label'],
846
+		);
847
+
848
+		if (!empty($this->search_params))
849
+			$url .= ';params=' . $this->search_params;
850
+	}
851
+
852
+	/**
853
+	 * Handle Search Params
854
+	 *
855
+	 * @internal
856
+	 * @CalledIn SMF 2.0, SMF 2.1
857
+	 * @version 1.1
858
+	 * @since 1.0
859
+	 * @return bool True upon success, false otherwise.
860
+	 */
861
+	private function handleLogSearchParams(): array
862
+	{
863
+		// If we have something to search for saved, get it back out.
864
+		if (!empty($_REQUEST['params']) && empty($_REQUEST['is_search']))
865
+		{
866
+			$search_params = base64_decode(strtr($_REQUEST['params'], array(' ' => '+')));
867
+			$search_params = $this->SFSclass->decodeJSON($search_params);
868
+
869
+			if (!empty($search_params))
870
+				return $search_params;
871
+		}
872
+	
873
+		return array();
874
+	}
875
+
876
+	/**
877
+	 * Handle Search Types
878
+	 *
879
+	 * @internal
880
+	 * @CalledIn SMF 2.0, SMF 2.1
881
+	 * @version 1.2
882
+	 * @since 1.0
883
+	 * @return array The valid Search Types.
884
+	 */
885
+	private function handleLogSearchTypes(): array
886
+	{
887
+		return array(
888
+			'url' => array('sql' => 'l.url', 'label' => $this->SFSclass->txt('sfs_log_search_url')),
889
+			'member' => array('sql' => 'mem.real_name', 'label' => $this->SFSclass->txt('sfs_log_search_member')),
890
+			'username' => array('sql' => 'l.username', 'label' => $this->SFSclass->txt('sfs_log_search_username')),
891
+			'email' => array('sql' => 'l.email', 'label' => $this->SFSclass->txt('sfs_log_search_email')),
892
+			'ip' => array('sql' => 'lm.ip', 'label' => $this->SFSclass->txt('sfs_log_search_ip')),
893
+			'ip2' => array('sql' => 'lm.ip2', 'label' => $this->SFSclass->txt('sfs_log_search_ip2'))
894
+		);
895
+	}
896
+ 
897
+	/**
898
+	 * Handle Search Params String
899
+	 *
900
+	 * @internal
901
+	 * @CalledIn SMF 2.0, SMF 2.1
902
+	 * @version 1.1
903
+	 * @since 1.0
904
+	 * @return string What we are searching for, validated and cleaned.
905
+	 */
906
+	private function handleLogSearchParamsString(): string
907
+	{
908
+		if (!isset($this->search_params['string']) || (!empty($_REQUEST['search']) && $this->search_params['string'] != $_REQUEST['search']))
909
+			return empty($_REQUEST['search']) ? '' : $_REQUEST['search'];
910
+		else
911
+			return $this->search_params['string'];
912
+	}
913
+
914
+	/**
915
+	 * Handle Search Params Type
916
+	 *
917
+	 * @internal
918
+	 * @CalledIn SMF 2.0, SMF 2.1
919
+	 * @version 1.1
920
+	 * @since 1.0
921
+	 * @return string The column we are searching.
922
+	 */
923
+	private function handleLogSearchParamsType(): string
924
+	{
925
+		global $context;
926
+
927
+		if (isset($_REQUEST['search_type']) || empty($this->search_params['type']) || !isset($this->search_types[$this->search_params['type']]))
928
+			return isset($_REQUEST['search_type']) && isset($this->search_types[$_REQUEST['search_type']]) ? $_REQUEST['search_type'] : (isset($this->search_types[$context['order']]) ? $context['order'] : 'member');
929
+		return $this->search_params['type'];
930
+	}
931
+}
0 932
\ No newline at end of file
... ...
@@ -6,7 +6,7 @@
6 6
  * @author SleePy <sleepy @ simplemachines (dot) org>
7 7
  * @copyright 2019
8 8
  * @license 3-Clause BSD https://opensource.org/licenses/BSD-3-Clause
9
- * @version 1.0.1
9
+ * @version 1.2
10 10
  */
11 11
 class SFS
12 12
 {
... ...
@@ -22,6 +22,15 @@ class SFS
22 22
 	private $softwareName = 'smf';
23 23
 	private $softwareVersion = '2.1';
24 24
 
25
+	/**
26
+	 * @var array The block Types.
27
+	 */
28
+	private $blockTypeMap = array(
29
+		'username' => 1,
30
+		'email' => 2,
31
+		'ip' => 3
32
+	);
33
+	
25 34
 	/**
26 35
 	 * Simple setup for the class to be used later correctly.
27 36
 	 * This simply loads the class into $smcFunc and we can grab this anywhere else later.
... ...
@@ -144,7 +153,7 @@ class SFS
144 153
 	 *
145 154
 	 * @api
146 155
 	 * @CalledIn SMF 2.0, SMF 2.1
147
-	 * @version 1.0
156
+	 * @version 1.2
148 157
 	 * @since 1.0
149 158
 	 * @uses create_control_verification - Hook SMF2.0
150 159
 	 * @uses integrate_create_control_verification_test - Hook SMF2.1
... ...
@@ -161,15 +170,16 @@ class SFS
161 170
 		// Get our options data.
162 171
 		$options = $this->getVerificationOptions();
163 172
 
164
-		// Posting?
165
-		if ($thisVerification['id'] == 'post' && in_array('post', $options))
166
-			return $this->checkVerificationTestPosts();
167
-		// reporting topics is only for guests.
168
-		elseif ($thisVerification['id'] == 'report' && in_array('report', $options))
169
-			return $this->checkVerificationTestReport();
170
-		// We should avoid this on searches, as we can only send ips.
171
-		elseif ($thisVerification['id'] == 'search' && in_array('search', $options) && ($user_info['is_guest'] || empty($user_info['posts']) || $user_info['posts'] < $modSettings['sfs_verfOptMemPostThreshold']))
172
-			return $this->checkVerificationTestSearch();
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($this, 'checkVerificationTest' . ucfirst($key));
173 183
 
174 184
 		// Others areas.  We have to play a guessing game here.
175 185
 		return $this->checkVerificationTestExtra($thisVerification);
... ...
@@ -184,7 +194,7 @@ class SFS
184 194
 	 * @since 1.1
185 195
 	 * @return bool True is success, no other bool is expeicifcly defined yet.
186 196
 	 */
187
-	private function checkVerificationTestPosts(): bool
197
+	private function checkVerificationTestPost(): bool
188 198
 	{
189 199
 		global $user_info, $modSettings;
190 200
 
... ...
@@ -312,7 +322,7 @@ class SFS
312 322
 	 *
313 323
 	 * @internal
314 324
 	 * @CalledIn SMF 2.0, SMF 2.1
315
-	 * @version 1.0
325
+	 * @version 1.2
316 326
 	 * @since 1.0
317 327
 	 * @return bool True is success, no other bool is expeicifcly defined yet.
318 328
 	 */
... ...
@@ -337,20 +347,18 @@ class SFS
337 347
 		$response = $this->sendSFSCheck($requestURL, $checks, $area);
338 348
 		$requestBlocked = '';
339 349
 
340
-		// Handle IPs only if we are supposed to, this is just a double check.
341
-		if (!empty($modSettings['sfs_ipcheck']) && !empty($response['ip']))
342
-			$requestBlocked = $this->sfsCheck_ips($response['ip']);
343
-
344
-		// If we didn't match a IP, handle Usernames only if we are supposed to, this is just a double check.
345
-		if (empty($requestBlocked) && !empty($modSettings['sfs_usernamecheck']) && !empty($response['username']))
346
-			$requestBlocked = $this->sfsCheck_username($response['username']);
350
+		$checkMap = array(
351
+			'ip' => !empty($modSettings['sfs_ipcheck']) && !empty($response['ip']),
352
+			'username' => !empty($modSettings['sfs_usernamecheck']) && !empty($response['username']),
353
+			'email' => !empty($modSettings['sfs_emailcheck']) && !empty($response['email'])
354
+		);
347 355
 
348
-		// If we didn't match a IP or username, handle Emails only if we are supposed to, this is just a double check.
349
-		if (empty($requestBlocked) && !empty($modSettings['sfs_emailcheck']) && !empty($response['email']))
350
-			$requestBlocked = $this->sfsCheck_email($response['email']);
356
+		// Run all the checks, if we should.
357
+		foreach ($checkMap as $key => $checkEnabled)
358
+			if (empty($requestBlocked) && $checkEnabled)
359
+				$requestBlocked = call_user_func(array($this, 'sfsCheck_' . $key), $response[$key], $area);
351 360
 
352 361
 		// Log all the stats?  Debug mode here.
353
-		if (!empty($modSettings['sfs_log_debug']))
354 362
 		$this->logAllStats('all', $checks, $requestBlocked);
355 363
 
356 364
 		// At this point, we have checked everything, do what needs to be done for our good person.
... ...
@@ -370,7 +378,7 @@ class SFS
370 378
 	 *
371 379
 	 * @internal
372 380
 	 * @CalledIn SMF 2.0, SMF 2.1
373
-	 * @version 1.1
381
+	 * @version 1.2
374 382
 	 * @since 1.1
375 383
 	 * @return array data we received back, could be a empty array.
376 384
 	 */
... ...
@@ -411,11 +419,11 @@ class SFS
411 419
 	 * @param string $area If defined the area we are checking.
412 420
 	 * @internal
413 421
 	 * @CalledIn SMF 2.0, SMF 2.1
414
-	 * @version 1.1
422
+	 * @version 1.2
415 423
 	 * @since 1.1
416 424
 	 * @return string Request Blocked data if any
417 425
 	 */
418
-	private function sfsCheck_ips(array $ips, string $area = ''): string
426
+	private function sfsCheck_ip(array $ips, string $area = ''): string
419 427
 	{
420 428
 		global $modSettings, $smcFunc;
421 429
 
... ...
@@ -423,8 +431,9 @@ class SFS
423 431
 		foreach ($ips as $check)
424 432
 		{
425 433
 			// They appeared! Block this.
426
-			if (!empty($check['appears']))
427
-			{
434
+			if (empty($check['appears']))
435
+				continue;
436
+
428 437
 			// Ban them because they are black listed?
429 438
 			$autoBlackListResult = '0';
430 439
 			if (!empty($modSettings['sfs_ipcheck_autoban']) && !empty($check['frequency']) && $check['frequency'] == 255)
... ...
@@ -434,7 +443,6 @@ class SFS
434 443
 			$requestBlocked = 'ip,' . $smcFunc['htmlspecialchars']($check['value']) . ',' . ($autoBlackListResult ? 1 : 0);
435 444
 			break;
436 445
 		}
437
-		}
438 446
 
439 447
 		return $requestBlocked;
440 448
 	}
... ...
@@ -446,7 +454,7 @@ class SFS
446 454
 	 * @param string $area If defined the area we are checking.
447 455
 	 * @internal
448 456
 	 * @CalledIn SMF 2.0, SMF 2.1
449
-	 * @version 1.1
457
+	 * @version 1.2
450 458
 	 * @since 1.1
451 459
 	 * @return string Request Blocked data if any
452 460
 	 */
... ...
@@ -458,21 +466,15 @@ class SFS
458 466
 		foreach ($usernames as $check)
459 467
 		{
460 468
 			// Combine with $area we could also require admin approval above thresholds on things like register.
461
-			if (!empty($check['appears']))
462
-			{
469
+			if (empty($check['appears']))
470
+				continue;
471
+
463 472
 			$shouldBlock = true;
464
-				$confidenceLevel = 0;
465 473
 
466
-				// They meet the confidence level, block them.
467
-				if (!empty($modSettings['sfs_username_confidence']) && !empty($check['confidence']) && $area == 'register' && (float) $modSettings['sfs_username_confidence'] <= (float) $check['confidence'])
468
-					$confidenceLevel = $check['confidence'];
469 474
 			// We are not confident that they should be blocked.
470 475
 			if (!empty($modSettings['sfs_username_confidence']) && !empty($check['confidence']) && $area == 'register' && (float) $modSettings['sfs_username_confidence'] > (float) $check['confidence'])
471 476
 			{
472
-					// Incase we need to debug this.
473
-					if (!empty($modSettings['sfs_log_debug']))
474 477
 				$this->logAllStats('all', $check, 'username,' . $smcFunc['htmlspecialchars']($check['value']) . ',' . $check['confidence']);
475
-
476 478
 				$shouldBlock = false;
477 479
 			}
478 480
 
... ...
@@ -480,11 +482,10 @@ class SFS
480 482
 			if ($shouldBlock)
481 483
 			{
482 484
 				$this->logBlockedStats('username', $check);
483
-					$requestBlocked = 'username,' . $smcFunc['htmlspecialchars']($check['value']) . ',' . $confidenceLevel;
485
+				$requestBlocked = 'username,' . $smcFunc['htmlspecialchars']($check['value']) . ',' . $check['confidence'];
484 486
 				break;
485 487
 			}
486 488
 		}
487
-		}
488 489
 
489 490
 		return $requestBlocked;
490 491
 	}
... ...
@@ -496,7 +497,7 @@ class SFS
496 497
 	 * @param string $area If defined the area we are checking.
497 498
 	 * @internal
498 499
 	 * @CalledIn SMF 2.0, SMF 2.1
499
-	 * @version 1.1
500
+	 * @version 1.2
500 501
 	 * @since 1.1
501 502
 	 * @return string Request Blocked data if any
502 503
 	 */
... ...
@@ -507,13 +508,13 @@ class SFS
507 508
 		$requestBlocked = '';
508 509
 		foreach ($email as $check)
509 510
 		{
510
-			if (!empty($check['appears']))
511
-			{
511
+			if (empty($check['appears']))
512
+				continue;
513
+
512 514
 			$this->logBlockedStats('email', $check);
513 515
 			$requestBlocked = 'email,' . $smcFunc['htmlspecialchars']($check['value']);
514 516
 			break;
515 517
 		}
516
-		}
517 518
 
518 519
 		return $requestBlocked;
519 520
 	}
... ...
@@ -527,7 +528,7 @@ class SFS
527 528
 	 *
528 529
 	 * @internal
529 530
 	 * @CalledIn SMF 2.0, SMF 2.1
530
-	 * @version 1.0
531
+	 * @version 1.2
531 532
 	 * @since 1.0
532 533
 	 * @return bool True we found something to check, false nothing..  $requestURL will be updated with the new data.
533 534
 	 */
... ...
@@ -541,11 +542,7 @@ class SFS
541 542
 			foreach ($chk as $type => $value)
542 543
 			{
543 544
 				// Hold up, we are not processing this check.
544
-				if (
545
-					($type == 'email' && empty($modSettings['sfs_emailcheck'])) ||
546
-					($type == 'username' && empty($modSettings['sfs_usernamecheck'])) ||
547
-					($type == 'ip' && empty($modSettings['sfs_ipcheck']))
548
-				)
545
+				if (in_array($type, array('email', 'username', 'ip')) && empty($modSettings['sfs_' . $type . 'check']))
549 546
 					continue;
550 547
 
551 548
 				// No value? Can't do this.
... ...
@@ -582,21 +579,7 @@ class SFS
582 579
 		global $smcFunc, $user_info;
583 580
 
584 581
 		// What type of log is this?
585
-		switch ($type)
586
-		{
587
-			case 'username':
588
-				$blockType = 1;
589
-				break;
590
-			case 'email':
591
-				$blockType = 2;
592
-				break;
593
-			case 'ip':
594
-				$blockType = 3;
595
-				break;
596
-			default:
597
-				$blockType = 99;
598
-				break;
599
-		}
582
+		$blockType = isset($this->blockTypeMap[$type]) ? $this->blockTypeMap[$type] : 99;
600 583
 
601 584
 		$smcFunc['db_insert']('',
602 585
 			'{db_prefix}log_sfs',
... ...
@@ -637,13 +620,16 @@ class SFS
637 620
 	 *
638 621
 	 * @internal
639 622
 	 * @CalledIn SMF 2.0, SMF 2.1
640
-	 * @version 1.0
623
+	 * @version 1.2
641 624
 	 * @since 1.0
642 625
 	 * @return bool True is success, no other bool is expeicifcly defined yet.
643 626
 	 */
644 627
 	private function logAllStats(string $type, array $checks, string $DebugMessage): void
645 628
 	{
646
-		global $smcFunc, $user_info;
629
+		global $modSettings, $smcFunc, $user_info;
630
+
631
+		if ($type == 'all' && empty($modSettings['sfs_log_debug']))
632
+			return;
647 633
 
648 634
 		$smcFunc['db_insert']('',
649 635
 			'{db_prefix}log_sfs',
... ...
@@ -713,7 +699,7 @@ class SFS
713 699
 	 * @internal
714 700
 	 * @link: https://www.stopforumspam.com/usage
715 701
 	 * @CalledIn SMF 2.0, SMF 2.1
716
-	 * @version 1.0
702
+	 * @version 1.2
717 703
 	 * @since 1.0
718 704
 	 * @return array The parsed json string is now an array.
719 705
 	 */
... ...
@@ -727,39 +713,35 @@ class SFS
727 713
 			return $url;
728 714
 
729 715
 		// Get our server info.
730
-		$this_server = $this->sfsServerMapping();
731
-		$server = $this_server[$modSettings['sfs_region']];
716
+		$server = $this->sfsServerMapping()[$modSettings['sfs_region']];
732 717
 
733 718
 		// Build the base URL, we always use json responses.
734 719
 		$url = 'https://' . $server['host'] . '/api?json';
735 720
 
736
-		// Ignore all wildcard checks?
737
-		if (!empty($modSettings['sfs_wildcard_email']) && !empty($modSettings['sfs_wildcard_username']) && !empty($modSettings['sfs_wildcard_ip']))
738
-			$url .= '&nobadall';
721
+		// All the SFS Urls => How we toggle them.
722
+		$sfsMap = array(
723
+			'nobadall' => !empty($modSettings['sfs_wildcard_email']) && !empty($modSettings['sfs_wildcard_username']) && !empty($modSettings['sfs_wildcard_ip']),
724
+			'notorexit' => !empty($modSettings['sfs_tor_check']) && $modSettings['sfs_tor_check'] == 1,
725
+			'badtorexit' => !empty($modSettings['sfs_tor_check']) && $modSettings['sfs_tor_check'] == 2,
726
+		);
727
+		foreach ($sfsMap as $val => $key)
728
+			if (!empty($key))
729
+				$url .= '&' . $val;
730
+
739 731
 		// Maybe only certain wildcards are ignored?
740
-		else
732
+		if (empty($sfsMap['nobadall']))
741 733
 		{
742
-			// Ignoring Wildcard Emails?
743
-			if (!empty($modSettings['sfs_wildcard_email']))
744
-				$url .= '&nobadusername';
745
-
746
-			// Ignoring Wildcard Usernames?
747
-			if (!empty($modSettings['sfs_wildcard_username']))
748
-				$url .= '&nobademail';
734
+			$ignoreMap = array(
735
+				'nobadusername' => !empty($modSettings['sfs_wildcard_email']),
736
+				'nobademail' => !empty($modSettings['sfs_wildcard_username']),
737
+				'nobadip' => !empty($modSettings['sfs_wildcard_ip']),
738
+			);
749 739
 
750
-			// Ignoring Wildcard IPs?
751
-			if (!empty($modSettings['sfs_wildcard_ip']))
752
-				$url .= '&nobadip';
740
+			foreach ($ignoreMap as $val => $key)
741
+				if (!empty($key))
742
+					$url .= '&' . $val;
753 743
 		}
754 744
 
755
-		// Tor handling, ignore them all.  Not recommended...
756
-		if (!empty($modSettings['sfs_tor_check']) && $modSettings['sfs_tor_check'] == 1)
757
-			$url .= '&notorexit';
758
-		// Only block bad exit nodes.
759
-		elseif (!empty($modSettings['sfs_tor_check']) && $modSettings['sfs_tor_check'] == 2)
760
-			$url .= '&badtorexit';
761
-		// Default handling for Tor is to block all exit nodes, nothing needed here.
762
-
763 745
 		// Do we have to filter out from lastseen?
764 746
 		if (!empty($modSettings['sfs_expire']))
765 747
 			$url .= '&expire=' . (int) $modSettings['sfs_expire'];
... ...
@@ -819,7 +801,7 @@ class SFS
819 801
 	 * @since 1.0
820 802
 	 * @return array The list of servers.
821 803
 	 */
822
-	private function getVerificationOptions()
804
+	private function getVerificationOptions(): array
823 805
 	{
824 806
 		global $user_info, $modSettings;
825 807
 
... ...
@@ -827,19 +809,18 @@ class SFS
827 809
 		$optionsKeyExtra = $user_info['is_guest'] ? 'sfs_verification_options_extra' : 'sfs_verOptionsMemExtra';
828 810
 
829 811
 		// Standard options.
812
+		$options = array();
830 813
 		if ($this->versionCheck('2.0', 'smf') && !empty($modSettings[$optionsKey]))
831 814
 			$options = safe_unserialize($modSettings[$optionsKey]);
832 815
 		elseif (!empty($modSettings[$optionsKey]))
833 816
 			$options = $this->decodeJSON($modSettings[$optionsKey]);
834
-		else
835
-			$options = array();
836 817
 
837 818
 		// Extras.
838 819
 		if (!empty($modSettings[$optionsKeyExtra]))
839 820
 		{
840 821
 			$this->extraVerificationOptions = explode(',', $modSettings[$optionsKeyExtra]);
841 822
 
842
-			if (is_array($options) && !empty($this->extraVerificationOptions))
823
+			if (!empty($this->extraVerificationOptions))
843 824
 				$options = array_merge($options, $this->extraVerificationOptions);
844 825
 		}
845 826
 
... ...
@@ -873,10 +854,11 @@ class SFS
873 854
 		);
874 855
 
875 856
 		// SMF 2.0 is serialized, SMF 2.1 is json.
857
+		$encodeFunc = 'json_encode';
876 858
 		if ($this->versionCheck('2.0', 'smf'))
877
-			$defaultSettings['sfs_verification_options'] = serialize(array('post'));
878
-		else
879
-			$defaultSettings['sfs_verification_options'] = json_encode(array('post'));
859
+			$encodeFunc = 'serialize';
860
+
861
+		$defaultSettings['sfs_verification_options'] = $encodeFunc(array('post'));
880 862
 		
881 863
 		// We undoing this? Maybe a save?
882 864
 		if ($undo)
... ...
@@ -985,7 +967,7 @@ class SFS
985 967
 	 *
986 968
 	 * @internal
987 969
 	 * @CalledIn SMF 2.0, SMF 2.1
988
-	 * @version 1.1
970
+	 * @version 1.2
989 971
 	 * @since 1.0
990 972
 	 * @return bool True upon success, false otherwise.
991 973
 	 */
... ...
@@ -1043,7 +1025,31 @@ class SFS
1043 1025
 
1044 1026
 		// Fall back.
1045 1027
 		if (is_array($ban_group_id) || empty($ban_group_id))
1028
+			$ban_group_id = $this->createBanGroupDirect($ban_info);
1029
+
1030
+		// Didn't work? Try again later.
1031
+		if (empty($ban_group_id))
1032
+			return false;
1033
+
1034
+		updateSettings(array('sfs_ipcheck_autoban_group' => $ban_group_id));
1035
+		return true;
1036
+	}
1037
+
1038
+	/**
1039
+	 * We failed to create a ban group via the API, do it manually.
1040
+	 *
1041
+	 * @param array $ban_info The ban info
1042
+	 *
1043
+	 * @internal
1044
+	 * @CalledIn SMF 2.0, SMF 2.1
1045
+	 * @version 1.2
1046
+	 * @since 1.2
1047
+	 * @return bool True upon success, false otherwise.
1048
+	 */
1049
+	public function createBanGroupDirect(array $ban_info): int
1046 1050
 	{
1051
+		global $smcFunc;
1052
+
1047 1053
 		$smcFunc['db_insert']('',
1048 1054
 			'{db_prefix}ban_groups',
1049 1055
 			array(
... ...
@@ -1057,15 +1063,7 @@ class SFS
1057 1063
 			array('id_ban_group'),
1058 1064
 			1
1059 1065
 		);
1060
-			$ban_group_id = $smcFunc['db_insert_id']('{db_prefix}ban_groups', 'id_ban_group');
1061
-		}
1062
-
1063
-		// Didn't work? Try again later.
1064
-		if (empty($ban_group_id))
1065
-			return false;
1066
-
1067
-		updateSettings(array('sfs_ipcheck_autoban_group' => $ban_group_id));
1068
-		return true;
1066
+		return $smcFunc['db_insert_id']('{db_prefix}ban_groups', 'id_ban_group');
1069 1067
 	}
1070 1068
 
1071 1069
 	/**
... ...
@@ -1076,7 +1074,7 @@ class SFS
1076 1074
 	 *
1077 1075
 	 * @internal
1078 1076
 	 * @CalledIn SMF 2.0, SMF 2.1
1079
-	 * @version 1.1
1077
+	 * @version 1.2
1080 1078
 	 * @since 1.0
1081 1079
 	 * @return bool True upon success, false otherwise.
1082 1080
 	 */
... ...
@@ -1084,23 +1082,56 @@ class SFS
1084 1082
 	{
1085 1083
 		global $smcFunc, $modSettings, $sourcedir;
1086 1084
 
1087
-		// Is this disabled? Don't do it.
1088
-		if (empty($modSettings['sfs_ipcheck_autoban']))
1089
-			return false;
1090
-
1091 1085
 		// Did we loose our Ban Group? Try to fix this.
1092
-		if (empty($modSettings['sfs_ipcheck_autoban_group']))
1086
+		if (!empty($modSettings['sfs_ipcheck_autoban']) && empty($modSettings['sfs_ipcheck_autoban_group']))
1093 1087
 			$this->createBanGroup();
1094 1088
 
1095 1089
 		// Still no Ban Group? Bail out.
1096
-		if (empty($modSettings['sfs_ipcheck_autoban_group']))
1090
+		if (empty($modSettings['sfs_ipcheck_autoban']) || empty($modSettings['sfs_ipcheck_autoban_group']))
1097 1091
 			return false;
1098 1092
 
1099 1093
 		require_once($sourcedir . '/ManageBans.php');
1100 1094
 
1101 1095
 		// If we have it, use the standard function.
1102
-		if (function_exists('insertBanGroup'))
1096
+		if (function_exists('addTriggers'))
1097
+			$result = $this->BanNewIPSMF21($ip_address);
1098
+		// Go old school.
1099
+		else
1100
+			$result = $this->BanNewIPSMF20($ip_address);
1101
+
1102
+		// Did this work?
1103
+		if ($result)
1104
+		{
1105
+			// Log this.  The log will show from the user/guest and ip of spammer.
1106
+			logAction('ban', array(
1107
+				'ip_range' => $ip_address,
1108
+				'new' => 1,
1109
+				'source' => 'sfs'
1110
+			));
1111
+
1112
+			// Let things know we need updated ban data.
1113
+			updateSettings(array('banLastUpdated' => time()));
1114
+			updateBanMembers();
1115
+		}
1116
+
1117
+		return true;
1118
+	}
1119
+
1120
+	/**
1121
+	 * Ban a IP with using some functions that exist in SMF 2.1.
1122
+	 *
1123
+	 * @param string $ip_address The IP address of the spammer.
1124
+	 *
1125
+	 * @internal
1126
+	 * @CalledIn SMF 2.0
1127
+	 * @version 1.2
1128
+	 * @since 1.2
1129
+	 * @return bool True upon success, false otherwise.
1130
+	 */
1131
+	private function BanNewIPSMF21(string $ip_address): bool
1103 1132
 	{
1133
+		global $smcFunc, $modSettings;
1134
+	
1104 1135
 		// We don't call checkExistingTriggerIP as it induces a fatal error.
1105 1136
 		$request = $smcFunc['db_query']('', '
1106 1137
 			SELECT bg.id_ban_group, bg.name
... ...
@@ -1131,10 +1162,25 @@ class SFS
1131 1162
 
1132 1163
 		// Add it.
1133 1164
 		addTriggers($modSettings['sfs_ipcheck_autoban_group'], $triggers);
1165
+
1166
+		return true;
1134 1167
 	}
1135
-		// Go old school.
1136
-		else
1168
+
1169
+	/**
1170
+	 * We need to fall back to standard db inserts to ban a user as the functions don't exist.
1171
+	 *
1172
+	 * @param string $ip_address The IP address of the spammer.
1173
+	 *
1174
+	 * @internal
1175
+	 * @CalledIn SMF 2.0
1176
+	 * @version 1.2
1177
+	 * @since 1.2
1178
+	 * @return bool True upon success, false otherwise.
1179
+	 */
1180
+	private function BanNewIPSMF20(string $ip_address): bool
1137 1181
 	{
1182
+		global $smcFunc, $modSettings;
1183
+
1138 1184
 		$ip_parts = ip2range($ip_address);
1139 1185
 
1140 1186
 		// Not valid? Get out.
... ...
@@ -1195,18 +1241,6 @@ class SFS
1195 1241
 			$ban_triggers,
1196 1242
 			array('id_ban')
1197 1243
 		);
1198
-		}
1199
-
1200
-		// Log this.  The log will show from the user/guest and ip of spammer.
1201
-		logAction('ban', array(
1202
-			'ip_range' => $ip_address,
1203
-			'new' => 1,
1204
-			'source' => 'sfs'
1205
-		));
1206
-
1207
-		// Let things know we need updated ban data.
1208
-		updateSettings(array('banLastUpdated' => time()));
1209
-		updateBanMembers();
1210 1244
 
1211 1245
 		return true;
1212 1246
 	}
... ...
@@ -1218,8 +1252,8 @@ class SFS
1218 1252
 	 *
1219 1253
 	 * @internal
1220 1254
 	 * @CalledIn SMF 2.0, SMF 2.1
1221
-	 * @version 1.1
1222
-	 * @since 1.0
1255
+	 * @version 1.2
1256
+	 * @since 1.2
1223 1257
 	 * @return bool True upon success, false otherwise.
1224 1258
 	 */
1225 1259
 	public function get(string $variable)
... ...
@@ -52,71 +52,160 @@ $smcFunc['db_create_table']($table['table_name'], $table['columns'], $table['ind
52 52
 /*
53 53
  * Calculates the proper settings to use in a column.
54 54
  *
55
+ * @version 1.2
55 56
  * @since 1.0
56 57
 */
57 58
 function db_field($name, $type, $size = 0, $unsigned = true, $auto = false)
58 59
 {
59 60
 	$fields = array(
60
-		'varchar' => array(
61
+		'varchar' => db_field_varchar($size, $unsigned, $auto),
62
+		'text' => db_field_text($size, $unsigned, $auto),
63
+		'mediumtext' => db_field_mediumtext($size, $unsigned, $auto),
64
+		'tinyint' => db_field_tinyint($size, $unsigned, $auto),
65
+		'smallint' => db_field_smallint($size, $unsigned, $auto),
66
+		'mediumint' => db_field_mediumint($size, $unsigned, $auto),
67
+		'int' => db_field_int($size, $unsigned, $auto),
68
+		'bigint' => db_field_bigint($size, $unsigned, $auto),
69
+	);
70
+
71
+	$field = $fields[$type];
72
+	$field['name'] = $name;
73
+
74
+	return $field;
75
+}
76
+
77
+/*
78
+ * Database Field - varchar.
79
+ *
80
+ * @version 1.2
81
+ * @since 1.2
82
+*/
83
+function db_field_varchar($size = 0, $unsigned = true, $auto = false)
84
+{
85
+	return array(
61 86
 		'auto' => false,
62 87
 		'type' => 'varchar',
63 88
 		'size' => $size == 0 ? 50 : $size,
64 89
 		'null' => false,
65
-		),
66
-		'text' => array(
90
+	);
91
+}
92
+
93
+/*
94
+ * Database Field - text.
95
+ *
96
+ * @version 1.2
97
+ * @since 1.2
98
+*/
99
+function db_field_text($size = 0, $unsigned = true, $auto = false)
100
+{
101
+	return array(
67 102
 		'auto' => false,
68 103
 		'type' => 'text',
69 104
 		'null' => false,
70
-		),
71
-		'mediumtext' => array(
105
+	);
106
+}
107
+
108
+/*
109
+ * Database Field - mediumtext.
110
+ *
111
+ * @version 1.2
112
+ * @since 1.2
113
+*/
114
+function db_field_mediumtext($size = 0, $unsigned = true, $auto = false)
115
+{
116
+	return array(
72 117
 		'auto' => false,
73 118
 		'type' => 'mediumtext',
74 119
 		'null' => false,
75
-		),
76
-		'tinyint' => array(
120
+	);
121
+}
122
+
123
+/*
124
+ * Database Field - tinyint.
125
+ *
126
+ * @version 1.2
127
+ * @since 1.2
128
+*/
129
+function db_field_tinyint($size = 0, $unsigned = true, $auto = false)
130
+{
131
+	return array(
77 132
 		'auto' => $auto,
78 133
 		'type' => 'tinyint',
79 134
 		'default' => 0,
80 135
 		'size' => empty($unsigned) ? 4 : 3,
81 136
 		'unsigned' => $unsigned,
82 137
 		'null' => false,
83
-		),
84
-		'smallint' => array(
138
+	);
139
+}
140
+
141
+/*
142
+ * Database Field - small int.
143
+ *
144
+ * @version 1.2
145
+ * @since 1.2
146
+*/
147
+function db_field_smallint($size = 0, $unsigned = true, $auto = false)
148
+{
149
+	return array(
85 150
 		'auto' => $auto,
86 151
 		'type' => 'smallint',
87 152
 		'default' => 0,
88 153
 		'size' => empty($unsigned) ? 6 : 5,
89 154
 		'unsigned' => $unsigned,
90 155
 		'null' => false,
91
-		),
92
-		'mediumint' => array(
156
+	);
157
+}
158
+
159
+/*
160
+ * Database Field - mediumn int.
161
+ *
162
+ * @version 1.2
163
+ * @since 1.2
164
+*/
165
+function db_field_mediumint($size = 0, $unsigned = true, $auto = false)
166
+{
167
+	return array(
93 168
 		'auto' => $auto,
94 169
 		'type' => 'mediumint',
95 170
 		'default' => 0,
96 171
 		'size' => 8,
97 172
 		'unsigned' => $unsigned,
98 173
 		'null' => false,
99
-		),
100
-		'int' => array(
174
+	);
175
+}
176
+
177
+/*
178
+ * Database Field - int.
179
+ *
180
+ * @version 1.2
181
+ * @since 1.2
182
+*/
183
+function db_field_int($size = 0, $unsigned = true, $auto = false)
184
+{
185
+	return array(
101 186
 		'auto' => $auto,
102 187
 		'type' => 'int',
103 188
 		'default' => 0,
104 189
 		'size' => empty($unsigned) ? 11 : 10,
105 190
 		'unsigned' => $unsigned,
106 191
 		'null' => false,
107
-		),
108
-		'bigint' => array(
192
+	);
193
+}
194
+
195
+/*
196
+ * Database Field - big int.
197
+ *
198
+ * @version 1.2
199
+ * @since 1.2
200
+*/
201
+function db_field_bigint($size = 0, $unsigned = true, $auto = false)
202
+{
203
+	return array(
109 204
 		'auto' => $auto,
110 205
 		'type' => 'bigint',
111 206
 		'default' => 0,
112 207
 		'size' => 21,
113 208
 		'unsigned' => $unsigned,
114 209
 		'null' => false,
115
-		),
116 210
 	);
117
-
118
-	$field = $fields[$type];
119
-	$field['name'] = $name;
120
-
121
-	return $field;
122 211
 }
123 212
\ No newline at end of file
... ...
@@ -40,17 +40,19 @@
40 40
 		<database>install_sfs.php</database>
41 41
 
42 42
 		<require-file name="language/StopForumSpam.english.php" destination="$themes_dir/default/languages" />
43
-		<require-file name="StopForumSpam.php" destination="$sourcedir" />
43
+		<require-file name="SFS.php" destination="$sourcedir" />
44
+		<require-file name="SFS-Subs-Admin.php" destination="$sourcedir" />
45
+		<require-file name="SFS-Subs-Logs.php" destination="$sourcedir" />
44 46
 
45 47
 		<!-- All the hooks -->
46 48
 			<!-- Main Section -->
47
-			<hook hook="integrate_pre_include" function="$sourcedir/StopForumSpam.php" />
49
+			<hook hook="integrate_pre_include" function="$sourcedir/SFS.php" />
48 50
 			<hook hook="integrate_pre_load" function="SFS::hook_pre_load" />
49 51
 			<hook hook="integrate_register" function="SFS::hook_register" />
50 52
 			<hook hook="integrate_create_control_verification_test" function="SFS::hook_create_control_verification_test" />
51 53
 
52 54
 			<!-- Admin Section -->
53
-			<hook hook="integrate_admin_include" function="$sourcedir/StopForumSpamAdmin.php" />
55
+			<hook hook="integrate_admin_include" function="$sourcedir/SFS-Subs-Admin.php" />
54 56
 			<hook hook="integrate_admin_areas" function="SFSA::hook_admin_areas" />
55 57
 			<hook hook="integrate_modify_modifications" function="SFSA::hook_modify_modifications" />
56 58
 			<hook hook="integrate_manage_logs" function="SFSA::hook_manage_logs" />
... ...
@@ -65,13 +67,13 @@
65 67
 
66 68
 		<!-- All the hooks, removed -->
67 69
 			<!-- Main Section -->
68
-			<hook hook="integrate_pre_include" function="$sourcedir/StopForumSpam.php" reverse="true" />
70
+			<hook hook="integrate_pre_include" function="$sourcedir/SFS.php" reverse="true" />
69 71
 			<hook hook="integrate_pre_load" function="SFS::hook_pre_load" reverse="true" />
70 72
 			<hook hook="integrate_register" function="SFS::hook_register" reverse="true" />
71 73
 			<hook hook="integrate_create_control_verification_test" function="SFSA::hook_create_control_verification_test" reverse="true" />
72 74
 
73 75
 			<!-- Admin Section -->
74
-			<hook hook="integrate_admin_include" function="$sourcedir/StopForumSpamAdmin.php" reverse="true" />
76
+			<hook hook="integrate_admin_include" function="$sourcedir/SFS-Subs-Admin.php" reverse="true" />
75 77
 			<hook hook="integrate_admin_areas" function="SFSA::hook_admin_areas" reverse="true" />
76 78
 			<hook hook="integrate_modify_modifications" function="SFSA::hook_modify_modifications" reverse="true" />
77 79
 			<hook hook="integrate_manage_logs" function="SFSA::hook_manage_logs" reverse="true" />
... ...
@@ -80,7 +82,9 @@
80 82
 		<remove-file name="$themes_dir/default/languages/StopForumSpam.english.php" />
81 83
 
82 84
 		<!-- source files, removed -->
83
-		<remove-file name="$sourcedir/StopForumSpam.php" />
85
+		<remove-file name="$sourcedir/SFS.php" />
86
+		<remove-file name="$sourcedir/SFS-Subs-Admin.php" />
87
+		<remove-file name="$sourcedir/SFS-Subs-Logs.php" />
84 88
 	</uninstall>
85 89
 
86 90
 </package-info>
87 91
\ No newline at end of file
... ...
@@ -20,12 +20,12 @@ if (SMF == 'SSI')
20 20
 
21 21
 $hooks = array(
22 22
 	// Main sections.
23
-	'integrate_pre_include' => '$sourcedir/StopForumSpam.php',
23
+	'integrate_pre_include' => '$sourcedir/SFS.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/StopForumSpamAdmin.php',
28
+	'integrate_admin_include' => '$sourcedir/SFS-Subs-Admin.php',
29 29
 	'integrate_admin_areas' => 'SFSA::hook_admin_areas',
30 30
 	'integrate_modify_modifications' => 'SFSA::hook_modify_modifications',
31 31
 	'integrate_manage_logs' => 'SFSA::hook_manage_logs'
... ...
@@ -20,12 +20,12 @@ if (SMF == 'SSI')
20 20
 
21 21
 $hooks = array(
22 22
 	// Main sections.
23
-	'integrate_pre_include' => '$sourcedir/StopForumSpam.php',
23
+	'integrate_pre_include' => '$sourcedir/SFS.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/StopForumSpamAdmin.php',
28
+	'integrate_admin_include' => '$sourcedir/SFS-Subs-Admin.php',
29 29
 	'integrate_admin_areas' => 'SFSA::hook_admin_areas',
30 30
 	'integrate_modify_modifications' => 'SFSA::hook_modify_modifications',
31 31
 	'integrate_manage_logs' => 'SFSA::hook_manage_logs'
32 32