Add the Stop Forum Spam mod
jdarwood007

jdarwood007 commited on 2019-12-01 20:26:18
Showing 9 changed files, with 1599 additions and 0 deletions.

... ...
@@ -0,0 +1,29 @@
1
+BSD 3-Clause License
2
+
3
+Copyright (c) 2019, SleePy
4
+All rights reserved.
5
+
6
+Redistribution and use in source and binary forms, with or without
7
+modification, are permitted provided that the following conditions are met:
8
+
9
+1. Redistributions of source code must retain the above copyright notice, this
10
+   list of conditions and the following disclaimer.
11
+
12
+2. Redistributions in binary form must reproduce the above copyright notice,
13
+   this list of conditions and the following disclaimer in the documentation
14
+   and/or other materials provided with the distribution.
15
+
16
+3. Neither the name of the copyright holder nor the names of its
17
+   contributors may be used to endorse or promote products derived from
18
+   this software without specific prior written permission.
19
+
20
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
... ...
@@ -0,0 +1,7 @@
1
+A customization for SMF 2.0 or 2.1.
2
+
3
+This has some setting compatibility with the original Stop Forum Spam as I was using it when I developed this.
4
+
5
+This version includes logging, bulk checks and the ability to check non standard SMF verification fields.
6
+
7
+This is in development as I develop its needs for SimpleDesk where I am primary using it.
0 8
\ No newline at end of file
... ...
@@ -0,0 +1,1218 @@
1
+<?php
2
+
3
+class SFS
4
+{
5
+	/* Some URLs */
6
+	private $urlSFSipCheck = 'https://www.stopforumspam.com/ipcheck/%1$s';
7
+	private $urlSFSsearch = 'https://www.stopforumspam.com/search/%1$s';
8
+
9
+	/* Our Software/Version Info defaults */
10
+	private $softwareName = 'smf';
11
+	private $softwareVersion = '2.1';
12
+
13
+	/* The admin page url */
14
+	private $adminPageURL = null;
15
+
16
+	/* Settings we defaulted*/
17
+	private $changedSettings = array();
18
+	private $extraVerificationOptions = array();
19
+
20
+	/* Search stuff */
21
+	private $search_params = array();
22
+	private $search_params_column = '';
23
+
24
+	/* Logs Disabled for */
25
+	private $hoursDisabled = 24;
26
+
27
+	/* Startup the class so we can call it later
28
+		@hook: SMF2.0: integrate_admin_areas
29
+		@hook: SMF2.1: integrate_admin_areas
30
+		@CalledIn: SMF 2.0, SMF 2.1
31
+	*/
32
+	public static function hook_pre_load()
33
+	{
34
+		global $smcFunc;
35
+		
36
+		$smcFunc['classSFS'] = new SFS();
37
+	}
38
+
39
+	/*
40
+		We do this once we construct
41
+		@CalledIn: SMF 2.0, SMF 2.1
42
+	*/
43
+	public function __construct()
44
+	{
45
+		global $smcFunc;
46
+
47
+		// Is this SMF 2.0?
48
+		if (!function_exists('loadCacheAccelerator'))
49
+			$this->softwareVersion = '2.0';
50
+
51
+		// Setup the defaults.
52
+		$this->loadDefaults();
53
+	}
54
+
55
+	/*
56
+		Admin Panel areas addition
57
+		@CalledIn: SMF 2.0, SMF 2.1
58
+		@hook: SMF2.0: integrate__admin_areas
59
+		@hook: SMF2.1: integrate__admin_areas
60
+	*/
61
+	public static function hook_admin_areas(&$admin_areas)
62
+	{
63
+		global $smcFunc;
64
+		return $smcFunc['classSFS']->setupAdminAreas($admin_areas);
65
+	}
66
+
67
+	/*
68
+		Does the actual setup of the admin areas
69
+		@CalledIn: SMF 2.0, SMF 2.1
70
+	*/
71
+	public function setupAdminAreas(&$admin_areas)
72
+	{
73
+		global $txt, $scripturl;
74
+
75
+		// Get our language in here.
76
+		$this->loadLanguage();
77
+
78
+		// Add the menu item.
79
+		if ($this->versionCheck('2.0', 'smf'))
80
+		{
81
+			$this->adminPageURL = $scripturl . '?action=admin;area=modsettings;sa=sfs';
82
+			$this->adminLogURL = $scripturl . '?action=admin;area=modsettings;sa=sfslog';
83
+
84
+			$admin_areas['config']['areas']['modsettings']['subsections']['sfs'] = array(
85
+				$txt['sfs_admin_area']
86
+			);
87
+			$admin_areas['config']['areas']['modsettings']['subsections']['sfslog'] = array(
88
+				$txt['sfs_admin_logs']
89
+			);
90
+		}
91
+		else
92
+		{
93
+			$this->adminPageURL = $scripturl . '?action=admin;area=securitysettings;sa=sfs';
94
+			$this->adminLogURL = $scripturl . '?action=admin;area=logs;sa=sfslog';
95
+
96
+			$admin_areas['config']['areas']['securitysettings']['subsections']['sfs'] = array(
97
+				$txt['sfs_admin_area']
98
+			);
99
+			$admin_areas['config']['areas']['securitysettings']['subsections']['sfslog'] = array(
100
+				$txt['sfs_admin_logs']
101
+			);
102
+		}
103
+
104
+		return;
105
+	}
106
+
107
+	/*
108
+		Only do this for 2.0, but we put it in the mod section.
109
+		@hook: SMF2.0: integrate_modify_modifications
110
+		@hook: SMF2.1: 
111
+		@CalledIn: SMF 2.0
112
+	*/
113
+	public static function hook_modify_modifications(&$subActions)
114
+	{
115
+		global $smcFunc;
116
+		return $smcFunc['classSFS']->setupModifyModifications($subActions);
117
+	}
118
+
119
+	/*
120
+		Setup the Configuration page.
121
+		@CalledIn: SMF 2.0
122
+	*/
123
+	public function setupModifyModifications(&$subActions)
124
+	{
125
+		$subActions['sfs'] = 'SFS::startupAdminConfiguration';
126
+
127
+		// Only in SMF 2.0 do we drop logs here.
128
+		if ($this->versionCheck('2.0', 'smf'))
129
+			$subActions['sfslog'] = 'SFS::startupLogs';
130
+
131
+		return;
132
+	}
133
+
134
+	/*
135
+		Only need to do this for SMF 2.0, SMF 2.1 calls hook_manage_logs
136
+		@hook: SMF2.0: integrate_modify_modifications
137
+		@CalledIn: SMF 2.0
138
+	*/
139
+	public static function startupAdminConfiguration($return_config = false)
140
+	{
141
+		global $smcFunc;
142
+		return $smcFunc['classSFS']->setupSFSConfiguration($return_config);
143
+	}
144
+
145
+	/*
146
+		The settings page.
147
+		@CalledIn: SMF 2.0, SMF 2.1
148
+	*/
149
+	public function setupSFSConfiguration($return_config = false)
150
+	{
151
+		global $txt, $scripturl, $context, $settings, $sc, $modSettings;
152
+
153
+		$config_vars = array(
154
+				array('title', 'sfsgentitle', 'label' => $txt['sfs_general_title']),
155
+
156
+				array('check', 'sfs_enabled'),
157
+				array('check', 'sfs_log_debug'),
158
+			'',
159
+				array('check', 'sfs_emailcheck'),
160
+				array('check', 'sfs_ipcheck'),
161
+				array('check', 'sfs_usernamecheck'),
162
+			'',
163
+				array('select', 'sfs_region', $this->sfsServerMapping('config')),
164
+			'',
165
+				array('check', 'sfs_wildcard_email'),
166
+				array('check', 'sfs_wildcard_username'),
167
+				array('check', 'sfs_wildcard_ip'),
168
+			'',
169
+				array('select', 'sfs_tor_check', array(
170
+					0 => $txt['sfs_tor_check_block'],
171
+					1 => $txt['sfs_tor_check_ignore'],
172
+					2 => $txt['sfs_tor_check_bad'],
173
+				)),
174
+
175
+			'',
176
+				array('title', 'sfsverftitle', 'label' => $txt['sfs_verification_title']),
177
+				array('desc', 'sfsverfdesc', 'label' => $txt['sfs_verification_desc']),
178
+				array('select', 'sfs_verification_options', array(
179
+					'post' => $txt['sfs_verification_options_post'],
180
+					'report' => $txt['sfs_verification_options_report'],
181
+					'search' => $txt['sfs_verification_options_search'],
182
+				), 'multiple' => true),			
183
+				array('text', 'sfs_verification_options_extra', 'subtext' => $txt['sfs_verification_options_extra_subtext']),
184
+
185
+			'',
186
+				array('select', 'sfs_verification_options_members', array(
187
+					'post' => $txt['sfs_verification_options_post'],
188
+					'search' => $txt['sfs_verification_options_search'],
189
+				), 'multiple' => true),
190
+				array('text', 'sfs_verification_options_membersextra', 'subtext' => $txt['sfs_verification_options_extra_subtext']),
191
+				array('int', 'sfs_verification_options_members_post_threshold'),
192
+		);
193
+
194
+		if ($return_config)
195
+			return $config_vars;
196
+
197
+		// Saving?
198
+		if (isset($_GET['save']))
199
+		{
200
+			// Turn the defaults off.
201
+			$this->unloadDefaults();
202
+			checkSession();
203
+
204
+			saveDBSettings($config_vars);
205
+
206
+			writeLog();
207
+			redirectexit($this->adminPageURL);
208
+		}
209
+
210
+		$context['post_url'] = $this->adminPageURL . ';save';
211
+
212
+		prepareDBSettingContext($config_vars);
213
+
214
+		return;
215
+	}
216
+
217
+	/*
218
+		In SMF 2.1 we do this hook.
219
+		@hook: SMF2.0:
220
+		@hook: SMF2.1: integrate_manage_logs
221
+		@CalledIn: SMF 2.1
222
+	*/
223
+	public static function hook_manage_logs(&$log_functions)
224
+	{
225
+		global $smcFunc;
226
+
227
+		$log_functions['sfslog'] = array('StopForumSpam.php', 'startupLogs');
228
+
229
+		$context[$context['admin_menu_name']]['tab_data']['tabs']['sfslog'] = array(
230
+			'description' => $txt['sfs_admin_logs'],
231
+		);
232
+
233
+		return;
234
+	}
235
+
236
+	/*
237
+		Show the logs as called by SMF from either hook_manage_logs (SMF 2.1) or setupModifyModifications (SMF 2.0)
238
+	*/
239
+	public static function startupLogs($return_config = false)
240
+	{
241
+		global $smcFunc;
242
+
243
+		// No Configs.
244
+		if ($return_config)
245
+			return array();
246
+
247
+		return $smcFunc['classSFS']->loadLogs();
248
+	}
249
+
250
+	/*
251
+		Actually load up logs
252
+	*/
253
+	public function loadLogs($return_config = false)
254
+	{
255
+		global $context, $txt, $smcFunc, $sourcedir;
256
+
257
+		// No Configs.
258
+		if ($return_config)
259
+			return array();
260
+
261
+		loadLanguage('Modlog');
262
+
263
+		$context['url_start'] = $this->adminLogURL;
264
+		$context['page_title'] = $txt['sfs_admin_logs'];
265
+		$context['can_delete'] = allowedTo('admin_forum');
266
+
267
+		// Remove all..
268
+		if (isset($_POST['removeall']) && $context['can_delete'])
269
+			$this->removeAllLogs();
270
+		elseif (!empty($_POST['remove']) && isset($_POST['delete']) && $context['can_delete'])
271
+			$this->removeLogs(array_unique($_POST['delete']));
272
+
273
+		$sort_types = array(
274
+			'id_type' =>'l.id_type',
275
+			'log_time' => 'l.log_time',
276
+			'member' => 'mem.id_member',
277
+			'username' => 'l.username',
278
+			'email' => 'l.email',
279
+			'ip' => 'l.ip',
280
+			'ip2' => 'l.ip2',
281
+		);
282
+
283
+		$context['order'] = isset($_REQUEST['sort']) && isset($sort_types[$_REQUEST['sort']]) ? $_REQUEST['sort'] : 'time';
284
+
285
+		// Handle searches.
286
+		$this->handleLogSearch();
287
+
288
+		require_once($sourcedir . '/Subs-List.php');
289
+
290
+		$listOptions = array(
291
+			'id' => 'sfslog_list',
292
+			'title' => $txt['sfs_admin_logs'],
293
+			'width' => '100%',
294
+			'items_per_page' => '50',
295
+			'no_items_label' => $txt['sfs_log_no_entries_found'],
296
+			'base_href' => $context['url_start'] . (!empty($context['search_params']) ? ';params=' . $context['search_params'] : ''),
297
+			'default_sort_col' => 'time',
298
+			'get_items' => array(
299
+				'function' => array($this, 'getSFSLogEntries'),
300
+				'params' => array(
301
+					(!empty($this->search_params['string']) ? ' INSTR({raw:sql_type}, {string:search_string})' : ''),
302
+					array('sql_type' => $this->search_params_column, 'search_string' => $this->search_params['string']),
303
+				),
304
+			),
305
+			'get_count' => array(
306
+				'function' => array($this, 'getSFSLogEntriesCount'),
307
+				'params' => array(
308
+					(!empty($this->search_params['string']) ? ' INSTR({raw:sql_type}, {string:search_string})' : ''),
309
+					array('sql_type' => $this->search_params_column, 'search_string' => $this->search_params['string']),
310
+				),
311
+			),
312
+			// This assumes we are viewing by user.
313
+			'columns' => array(
314
+				'type' => array(
315
+					'header' => array(
316
+						'value' => $txt['sfs_log_header_type'],
317
+						'class' => 'lefttext',
318
+					),
319
+					'data' => array(
320
+						'db' => 'type',
321
+						'class' => 'smalltext',
322
+					),
323
+					'sort' => array(
324
+					),
325
+				),
326
+				'time' => array(
327
+					'header' => array(
328
+						'value' => $txt['sfs_log_header_time'],
329
+						'class' => 'lefttext',
330
+					),
331
+					'data' => array(
332
+						'db' => 'time',
333
+						'class' => 'smalltext',
334
+					),
335
+					'sort' => array(
336
+						'default' => 'l.log_time DESC',
337
+						'reverse' => 'l.log_time',
338
+					),
339
+				),
340
+				'member' => array(
341
+					'header' => array(
342
+						'value' => $txt['sfs_log_header_member'],
343
+						'class' => 'lefttext',
344
+					),
345
+					'data' => array(
346
+						'db' => 'member_link',
347
+						'class' => 'smalltext',
348
+					),
349
+					'sort' => array(
350
+						'default' => 'mem.id_member',
351
+						'reverse' => 'mem.id_member DESC',
352
+					),
353
+				),
354
+				'username' => array(
355
+					'header' => array(
356
+						'value' => $txt['sfs_log_header_username'],
357
+						'class' => 'lefttext',
358
+					),
359
+					'data' => array(
360
+						'db' => 'username',
361
+						'class' => 'smalltext',
362
+					),
363
+					'sort' => array(
364
+						'default' => 'l.username',
365
+						'reverse' => 'l.username DESC',
366
+					),
367
+				),
368
+				'email' => array(
369
+					'header' => array(
370
+						'value' => $txt['sfs_log_header_email'],
371
+						'class' => 'lefttext',
372
+					),
373
+					'data' => array(
374
+						'db' => 'email',
375
+						'class' => 'smalltext',
376
+					),
377
+					'sort' => array(
378
+						'default' => 'l.email',
379
+						'reverse' => 'l.email DESC',
380
+					),
381
+				),
382
+				'ip' => array(
383
+					'header' => array(
384
+						'value' => $txt['sfs_log_header_ip'],
385
+						'class' => 'lefttext',
386
+					),
387
+					'data' => array(
388
+						'db' => 'ip',
389
+						'class' => 'smalltext',
390
+					),
391
+					'sort' => array(
392
+						'default' => 'l.ip',
393
+						'reverse' => 'l.ip DESC',
394
+					),
395
+				),
396
+				'ip2' => array(
397
+					'header' => array(
398
+						'value' => $txt['sfs_log_header_ip2'],
399
+						'class' => 'lefttext',
400
+					),
401
+					'data' => array(
402
+						'db' => 'ip2',
403
+						'class' => 'smalltext',
404
+					),
405
+					'sort' => array(
406
+						'default' => 'l.ip2',
407
+						'reverse' => 'l.ip2 DESC',
408
+					),
409
+				),
410
+				'checks' => array(
411
+					'header' => array(
412
+						'value' => $txt['sfs_log_checks'],
413
+						'class' => 'lefttext',
414
+					),
415
+					'data' => array(
416
+						'db' => 'checks',
417
+						'class' => 'smalltext',
418
+					),
419
+					'sort' => array(),
420
+				),
421
+				'result' => array(
422
+					'header' => array(
423
+						'value' => $txt['sfs_log_result'],
424
+						'class' => 'lefttext',
425
+					),
426
+					'data' => array(
427
+						'db' => 'result',
428
+						'class' => 'smalltext',
429
+					),
430
+					'sort' => array(),
431
+				),
432
+				'delete' => array(
433
+					'header' => array(
434
+						'value' => '<input type="checkbox" name="all" class="input_check" onclick="invertAll(this, this.form);" />',
435
+					),
436
+					'data' => array(
437
+						'function' => create_function('$entry', '
438
+							return \'<input type="checkbox" class="input_check" name="delete[]" value="\' . $entry[\'id\'] . \'"\' . ($entry[\'editable\'] ? \'\' : \' disabled="disabled"\') . \' />\';
439
+						'),
440
+						'style' => 'text-align: center;',
441
+					),
442
+				),
443
+			),
444
+			'form' => array(
445
+				'href' => $context['url_start'],
446
+				'include_sort' => true,
447
+				'include_start' => true,
448
+				'hidden_fields' => array(
449
+					$context['session_var'] => $context['session_id'],
450
+					'params' => $context['search_params']
451
+				),
452
+			),
453
+			'additional_rows' => array(
454
+				array(
455
+					'position' => 'below_table_data',
456
+					'value' => '
457
+						' . $txt['sfs_log_search'] . ' (' . $context['search']['label'] . '):
458
+						<input type="text" name="search" size="18" value="' . $smcFunc['htmlspecialchars']($context['search']['string']) . '" class="input_text" /> <input type="submit" name="is_search" value="' . $txt['modlog_go'] . '" class="button_submit" />
459
+						' . ($context['can_delete'] ? ' |
460
+							<input type="submit" name="remove" value="' . $txt['modlog_remove'] . '" class="button_submit" />
461
+							<input type="submit" name="removeall" value="' . $txt['modlog_removeall'] . '" class="button_submit" />' : ''),
462
+				),
463
+			),
464
+		);
465
+
466
+		// Create the watched user list.
467
+		createList($listOptions);
468
+
469
+		$context['sub_template'] = 'show_list';
470
+		$context['default_list'] = 'sfslog_list';
471
+	}
472
+
473
+	/*
474
+		Get the Log entries
475
+	*/
476
+	public function getSFSLogEntries($start, $items_per_page, $sort, $query_string = '', $query_params = array())
477
+	{
478
+		global $context, $smcFunc, $txt;
479
+
480
+		$result = $smcFunc['db_query']('', '
481
+			SELECT
482
+				l.id_sfs,
483
+				l.id_type,
484
+				l.log_time,
485
+				l.id_member,
486
+				l.username,
487
+				l.email,
488
+				l.ip,
489
+				l.ip2,
490
+				l.checks,
491
+				l.result,
492
+				mem.real_name,
493
+				mg.group_name
494
+			FROM {db_prefix}log_sfs AS l
495
+				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = l.id_member)
496
+				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)
497
+				WHERE id_type IS NOT NULL'
498
+				. (!empty($query_string) ? '
499
+					AND ' . $query_string : '') . '
500
+			ORDER BY ' . $sort . '
501
+			LIMIT ' . $start . ', ' . $items_per_page,
502
+			array_merge($query_params, array(
503
+				'reg_group_id' => 0,
504
+			))
505
+		);
506
+
507
+		while ($row = $smcFunc['db_fetch_assoc']($result))
508
+		{
509
+			$entries[$row['id_sfs']] = array(
510
+				'id' => $row['id_sfs'],
511
+				'type' => $txt['sfs_log_types_' . $row['id_type']],
512
+				'time' => timeformat($row['log_time']),
513
+				'timestamp' => forum_time(true, $row['log_time']),
514
+				'member_link' => $row['id_member'] ? '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>' : (empty($row['real_name']) ? ($txt['guest'] . (!empty($row['extra']['member_acted']) ? ' (' . $row['extra']['member_acted'] . ')' : '')) : $row['real_name']),
515
+				'username' => $row['username'],
516
+				'email' => $row['email'],
517
+				'ip' => '<a href="' . sprintf($this->urlSFSipCheck, $row['ip']) . '">' . $row['ip'] . '</a>',
518
+				'ip2' => '<a href="' . sprintf($this->urlSFSipCheck, $row['ip2']) . '">' . $row['ip2'] . '</a>',
519
+				'editable' => true, //time() > $row['log_time'] + $this->hoursDisabled * 3600,
520
+				'checks_raw' => $row['checks'],
521
+				'result_raw' => $row['result'],
522
+			);
523
+
524
+			$checksDecoded = $this->decodeJSON($row['checks']);
525
+
526
+			// Checks, username
527
+			if ($row['id_type'] == 1)
528
+				$entries[$row['id_sfs']]['checks'] = '<a href="' . sprintf($this->urlSFSsearch, $checksDecoded['value']) . '">' . $checksDecoded['value'] . '</a>';
529
+			elseif ($row['id_type'] == 2)
530
+				$entries[$row['id_sfs']]['checks'] = '<a href="' . sprintf($this->urlSFSsearch, $checksDecoded['value']) . '">' . $checksDecoded['value'] . '</a>';
531
+			elseif ($row['id_type'] == 3)
532
+				$entries[$row['id_sfs']]['checks'] = '<a href="' . sprintf($this->urlSFSsearch, $checksDecoded['value']) . '">' . $checksDecoded['value'] . '</a>';
533
+			else
534
+			{
535
+				$entries[$row['id_sfs']]['checks'] = '';
536
+
537
+				foreach ($checksDecoded as $key => $vkey)
538
+					foreach ($vkey as $key => $value)
539
+						$entries[$row['id_sfs']]['checks'] .= ucfirst($key) . ':' . $value . '<br>';					
540
+			}
541
+
542
+			// $results
543
+			if (strpos($row['result'], ',') !== false)
544
+			{
545
+				list($resultType, $resultMatch) = explode(',', $row['result']);
546
+				$entries[$row['id_sfs']]['result'] = 'Matched on ' . $resultType . ' [' . $resultMatch . ']';
547
+			}
548
+			else
549
+				$entries[$row['id_sfs']]['result'] = $row['result'];
550
+			
551
+		}
552
+		$smcFunc['db_free_result']($result);
553
+
554
+		return $entries;
555
+	}
556
+
557
+	/*
558
+		Get the Counter Log entries
559
+	*/
560
+	public function getSFSLogEntriesCount($query_string = '', $query_params = array(), $log_type = 1)
561
+	{
562
+		global $smcFunc, $user_info;
563
+
564
+		$result = $smcFunc['db_query']('', '
565
+			SELECT COUNT(*)
566
+			FROM {db_prefix}log_sfs AS l
567
+				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = l.id_member)
568
+				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)
569
+				WHERE id_type IS NOT NULL'
570
+				. (!empty($query_string) ? '
571
+					AND ' . $query_string : ''),
572
+			array_merge($query_params, array(
573
+				'reg_group_id' => 0,
574
+			))
575
+		);
576
+		list ($entry_count) = $smcFunc['db_fetch_row']($result);
577
+		$smcFunc['db_free_result']($result);
578
+
579
+		return $entry_count;
580
+	}
581
+
582
+	/*
583
+		Remove all logs, except for those 24 horus or newer.
584
+	*/
585
+	private function removeAllLogs()
586
+	{
587
+		global $smcFunc;
588
+
589
+		checkSession();
590
+
591
+		$smcFunc['db_query']('', '
592
+			DELETE FROM {db_prefix}log_sfs
593
+			WHERE log_time < {int:twenty_four_hours_wait}',
594
+			array(
595
+				'twenty_four_hours_wait' => time() - $this->hoursDisabled * 3600,
596
+			)
597
+		);
598
+	}
599
+
600
+	/*
601
+		Remove specific logs, except for those 24 horus or newer.
602
+	*/
603
+	private function removeLogs($entries)
604
+	{
605
+		global $smcFunc;
606
+
607
+		checkSession();
608
+
609
+		$smcFunc['db_query']('', '
610
+			DELETE FROM {db_prefix}log_sfs
611
+			WHERE id_sfs IN ({array_string:delete_actions})
612
+				AND log_time < {int:twenty_four_hours_wait}',
613
+			array(
614
+				'twenty_four_hours_wait' => time() - $this->hoursDisabled * 3600,
615
+				'delete_actions' => $entries,
616
+			)
617
+		);
618
+	}
619
+
620
+	/*
621
+		Handle registration events
622
+		@CalledIn: SMF 2.0, SMF 2.1
623
+		@calledAt: action=signup, action=admin;area=regcenter;sa=register
624
+		@hook: SMF2.0: integrate_register
625
+		@hook: SMF2.1: integrate_register
626
+	*/
627
+	public static function hook_register(&$regOptions, &$theme_vars)
628
+	{
629
+		global $smcFunc;
630
+		return $smcFunc['classSFS']->checkRegisterRequest($regOptions, $theme_vars);
631
+	}
632
+
633
+	/*
634
+		Something is attempting to register, we should check them out.
635
+	*/
636
+	public function checkRegisterRequest(&$regOptions, &$theme_vars)
637
+	{
638
+		// Admins are not spammers.. usually.
639
+		if ($regOptions['interface'] == 'admin')
640
+			return true;
641
+		// Get our language in here.
642
+		$this->loadLanguage();
643
+
644
+		// Pass everything and let us handle what options we pass on.  We pass the register_vars as these are what we have cleaned up.
645
+		$this->sfsCheck(array(
646
+			array('username' => $regOptions['register_vars']['member_name']),
647
+			array('email' => $regOptions['register_vars']['email_address']),
648
+			array('ip' => $regOptions['register_vars']['member_ip']),
649
+			array('ip' => $regOptions['register_vars']['member_ip2']),
650
+		), 'register');
651
+	}
652
+
653
+	/*
654
+		Handle verification events, except register.
655
+		@CalledIn: SMF 2.1
656
+		@hook: SMF2.1: integrate_create_control_verification_test
657
+	*/
658
+	public static function hook_create_control_verification_test($thisVerification, &$verification_errors)
659
+	{
660
+		global $smcFunc;
661
+		$smcFunc['classSFS']->checkVerificationTest($thisVerification, $verification_errors);
662
+	}
663
+	
664
+	/*
665
+		Something is attempting to post, we should check them out.
666
+		SMF 2.0 calls this directly as it doesnn't have a hook.
667
+	*/
668
+	public function checkVerificationTest($thisVerification, &$verification_errors)
669
+	{
670
+		global $user_info;
671
+
672
+		// Registration is skipped as we process that differently.
673
+		if ($thisVerification['id'] == 'register')
674
+			return true;
675
+
676
+		// Get our language in here.
677
+		$this->loadLanguage();
678
+
679
+		$options = $this->getVerificationOptions();
680
+
681
+		// Posting?
682
+		if ($thisVerification['id'] == 'post' && in_array('post', $options))
683
+		{
684
+			// Guests!
685
+			if ($user_info['is_guest'])
686
+			{
687
+				$guestname = !isset($_POST['guestname']) ? '' : trim($_POST['guestname']);
688
+				$email = !isset($_POST['email']) ? '' : trim($_POST['email']);
689
+
690
+				return $this->sfsCheck(array(
691
+					array('username' => $guestname),
692
+					array('email' => $email),
693
+					array('ip' => $user_info['ip']),
694
+					array('ip' => $user_info['ip2']),
695
+				), 'post');
696
+				
697
+			}
698
+			// Members and they don't have enough posts?
699
+			elseif (empty($user_info['posts']) || $user_info['posts'] < $modSettings['sfs_verification_options_members_post_threshold'])
700
+				return $this->sfsCheck(array(
701
+					array('username' => $user_info['username']),
702
+					array('email' => $user_info['email']),
703
+					array('ip' => $user_info['ip']),
704
+					array('ip' => $user_info['ip2']),
705
+				), 'post');
706
+			else
707
+				return true;
708
+		}
709
+		// reporting topics is only for guests.
710
+		elseif ($thisVerification['id'] == 'report' && in_array('report', $options))
711
+		{
712
+			$email = !isset($_POST['email']) ? '' : trim($_POST['email']);
713
+
714
+			return $this->sfsCheck(array(
715
+				array('email' => $email),
716
+				array('ip' => $user_info['ip']),
717
+				array('ip' => $user_info['ip2']),
718
+			), 'post');
719
+		}
720
+		// We should avoid this on searches, as we can only send ips.
721
+		elseif ($thisVerification['id'] == 'search' && in_array('search', $options) && ($user_info['is_guest'] || empty($user_info['posts']) || $user_info['posts'] < $modSettings['sfs_verification_options_members_post_threshold']))
722
+		{
723
+			return $this->sfsCheck(array(
724
+				array('ip' => $user_info['ip']),
725
+				array('ip' => $user_info['ip2']),
726
+			), 'search');
727
+		}
728
+
729
+		// Others areas.  We have to play a guessing game here.
730
+		foreach ($this->extraVerificationOptions as $option)
731
+		{
732
+			// Not a match.
733
+			if ($thisVerification['id'] != $option)
734
+				continue;
735
+
736
+			// Always try to send off IPs.
737
+			$checks = array(
738
+				array('ip' => $user_info['ip']),
739
+				array('ip' => $user_info['ip2']),
740
+			);
741
+
742
+			// Can we find a username?
743
+			$possibleUserNames = array('username', 'user_name', 'user', 'name', 'realname');
744
+			foreach ($possibleUserNames as $searchKey)
745
+				if (!empty($_POST[$searchKey]))
746
+				{
747
+					$checks[] = array('username', $_POST[$searchKey]);
748
+					break;
749
+				}
750
+
751
+			// Can we find a email?
752
+			$possibleUserNames = array('email', 'emailaddress', 'email_address');
753
+			foreach ($possibleUserNames as $searchKey)
754
+				if (!empty($_POST[$searchKey]))
755
+				{
756
+					$checks[] = array('email', $_POST[$searchKey]);
757
+					break;
758
+				}
759
+
760
+			return $this->sfsCheck($checks, $option);
761
+		}
762
+die;
763
+	}
764
+
765
+	/*
766
+		Check data against the SFS database
767
+	*/
768
+	public function sfsCheck($checks, $area = null)
769
+	{
770
+		global $sourcedir, $smcFunc, $context, $modSettings, $txt;
771
+
772
+		$requestURL = $this->buildServerURL();
773
+
774
+		// Lets build our data set, always send it as a bulk.
775
+		$singleCheckFound = false;
776
+		foreach ($checks as $chk)
777
+		{
778
+			foreach ($chk as $type => $value)
779
+			{
780
+				// Hold up, we are not processing this check.
781
+				if (
782
+					($type == 'email' && empty($modSettings['sfs_emailcheck'])) ||
783
+					($type == 'username' && empty($modSettings['sfs_usernamecheck'])) ||
784
+					($type == 'ip' && empty($modSettings['sfs_ipcheck']))
785
+				)
786
+					continue;
787
+
788
+				// No value? Can't do this.
789
+				if (empty($value))
790
+					continue;
791
+
792
+				// Emails and usernames must be UTF-8, Only a issue with SMF 2.0.
793
+				if (!$context['utf8'] && ($type == 'email' || $type == 'username'))
794
+					$requestURL .= '&' . $type . '[]=' . iconv($context['character_set'], 'UTF-8//IGNORE', $value);
795
+				else
796
+					$requestURL .= '&' . $type . '[]=' . urlencode($value);
797
+
798
+				$singleCheckFound = true;
799
+			}
800
+		}
801
+
802
+		// No checks found? Can't do this.
803
+		if (empty($singleCheckFound))
804
+		{
805
+			$this->logAllStats('error', $checks, 'error');
806
+			log_error($txt['sfs_request_failure_nodata'] . ':' . $requestURL, 'critical');
807
+			return true;
808
+		}
809
+
810
+		// SMF 2.0 has the fetch_web_data in the Subs-Packages, 2.1 it is in Subs.php.
811
+		if ($this->versionCheck('2.0', 'smf'))
812
+			require_once($sourcedir . '/Subs-Package.php');
813
+		
814
+		// Now we have a URL, lets go get it.
815
+		$response = $this->decodeJSON(fetch_web_data($requestURL));
816
+
817
+		// No data received, log it and let them through.
818
+		if (empty($response))
819
+		{
820
+			$this->logAllStats('error', $checks, 'failure');
821
+			log_error($txt['sfs_request_failure'] . ':' . $requestURL, 'critical');
822
+			return true;
823
+		}
824
+
825
+		$requestBlocked = false;
826
+
827
+		// Handle IPs only if we are supposed to, this is just a double check.
828
+		if (!empty($modSettings['sfs_ipcheck']) && !empty($response['ip']))
829
+		{
830
+			foreach ($response['ip'] as $check)
831
+			{
832
+				// !!! TODO: Frequency 255 is a blacklist, maybe add them to a generic ban list?
833
+				if (!empty($check['appears']))
834
+				{
835
+					$this->logBlockedStats('ip', $check);
836
+					$requestBlocked = 'ip,' . $smcFunc['htmlspecialchars']($check['value']);
837
+					break;
838
+				}
839
+			}
840
+		}
841
+
842
+		// If we didn't match a IP, handle Usernames only if we are supposed to, this is just a double check.
843
+		if (empty($requestBlocked) && !empty($modSettings['sfs_usernamecheck']) && !empty($response['username']))
844
+		{
845
+			foreach ($response['username'] as $check)
846
+			{
847
+				// !!! TODO: Expose confidence as a threshold?
848
+				// Combine with $area we could also require admin approval above thresholds on things like register.
849
+				// !!! TODO: Expose lastseen as a threshold?
850
+				if (!empty($check['appears']))
851
+				{
852
+					$this->logBlockedStats('username', $check);
853
+					$requestBlocked = 'username,' . $smcFunc['htmlspecialchars']($check['value']);
854
+					break;
855
+				}
856
+			}
857
+		}
858
+
859
+		// If we didn't match a IP or username, handle Emails only if we are supposed to, this is just a double check.
860
+		if (empty($requestBlocked) && !empty($modSettings['sfs_emailcheck']) && !empty($response['email']))
861
+		{
862
+			foreach ($response['email'] as $check)
863
+			{
864
+				if (!empty($check['appears']))
865
+				{
866
+					$this->logBlockedStats('email', $check);
867
+					$requestBlocked = 'email,' . $smcFunc['htmlspecialchars']($check['value']);
868
+					break;
869
+				}
870
+			}
871
+		}
872
+
873
+
874
+		// Log all the stats?  Debug mode here.
875
+		if (!empty($modSettings['sfs_log_debug']))
876
+			$this->logAllStats('all', $checks, $requestBlocked);
877
+
878
+		// At this point, we have checked everything, do what needs to be done for our good person.
879
+		if (!$requestBlocked)
880
+			return true;
881
+
882
+		// You are a bad spammer, don't tell them what was blocked.
883
+		$this->loadLanguage();
884
+		fatal_error($txt['sfs_request_blocked']);
885
+	}
886
+
887
+	/*
888
+		Log the blocked request for later
889
+	*/
890
+	private function logBlockedStats($type, $check)
891
+	{
892
+		global $smcFunc, $user_info;
893
+
894
+		switch($type)
895
+		{
896
+			case 'username':
897
+				$blockType = 1;
898
+				break;
899
+			case 'email':
900
+				$blockType = 2;
901
+				break;
902
+			case 'ip':
903
+				$blockType = 3;
904
+				break;
905
+			default:
906
+				$blockType = 99;
907
+				break;
908
+		}
909
+
910
+		$smcFunc['db_insert']('',
911
+			'{db_prefix}log_sfs',
912
+			array('id_type' => 'int', 'log_time' => 'int', 'id_member' => 'int', 'username' => 'string', 'email' => 'string', 'ip' => 'string', 'ip2' => 'string', 'checks' => 'string', 'result' => 'string'),
913
+			array(
914
+				$blockType, // Blocked request
915
+				time(),
916
+				$user_info['id'],
917
+				$type == 'username' ? $check['value'] : '',
918
+				$type == 'email' ? $check['value'] : '',
919
+				$type == 'ip' ? $check['value'] : $user_info['ip'],
920
+				$user_info['ip2'],
921
+				json_encode($check),
922
+				'Blocked'
923
+				),
924
+			array('id_sfs', 'id_type')
925
+		);
926
+	}
927
+
928
+	/*
929
+		Log all the data for later.
930
+	*/
931
+	private function logAllStats($type, $checks, $requestBlocked)
932
+	{
933
+		global $smcFunc, $user_info;
934
+
935
+		$smcFunc['db_insert']('',
936
+			'{db_prefix}log_sfs',
937
+			array('id_type' => 'int', 'log_time' => 'int', 'id_member' => 'int', 'username' => 'string', 'email' => 'string', 'ip' => 'string', 'ip2' => 'string', 'checks' => 'string', 'result' => 'string'),
938
+			array(
939
+				0, // Debug type.
940
+				time(),
941
+				$user_info['id'],
942
+				'', // Username
943
+				'', // email
944
+				$user_info['ip'],
945
+				$user_info['ip2'],
946
+				json_encode($checks),
947
+				$requestBlocked,
948
+				),
949
+			array('id_sfs', 'id_type')
950
+		);
951
+	}
952
+
953
+	/*
954
+		Decode JSON data.
955
+		If we have $smcFunc['json_decode'] we use it as it can handle errors.
956
+		Otherwise we do some basic stuff.
957
+	*/
958
+	private function decodeJSON($requestData)
959
+	{
960
+		global $smcFunc;
961
+
962
+		// Do we have $smcFunc?  It handles errors and logs them as needed.
963
+		if (isset($smcFunc['json_decode']) && is_callable($smcFunc['json_decode']))
964
+			return $smcFunc['json_decode']($request, true);
965
+		// Back to the basics.
966
+		else
967
+		{
968
+			$data = @json_decode($requestData, true);
969
+
970
+			// We got a error, return nothing.
971
+			// !!! TODO: Log this?
972
+			if (json_last_error() !== JSON_ERROR_NONE)
973
+				return array();
974
+			return $data;
975
+		}
976
+	}
977
+
978
+	/*
979
+		Build the base URL for the Stop Forum Spam website
980
+		@resource: https://www.stopforumspam.com/usage
981
+	*/
982
+	public function buildServerURL()
983
+	{
984
+		global $modSettings;
985
+		static $url = null;
986
+
987
+		// If we build this once, don't do it again.
988
+		if (!empty($url))
989
+			return $url;
990
+
991
+		// Get our server info.
992
+		$this_server = $this->sfsServerMapping();
993
+		$server = $this_server[$modSettings['sfs_region']];
994
+
995
+		// Build the base URL, we always use json responses.
996
+		$url = 'https://' . $server['host'] . '/api?json';
997
+
998
+		// Ignore all wildcard checks?
999
+		if (!empty($modSettings['sfs_wildcard_email']) && !empty($modSettings['sfs_wildcard_username'])  && !empty($modSettings['sfs_wildcard_ip']))
1000
+			$url .= '&nobadall';
1001
+		// Maybe only certain wildcards are ignored?
1002
+		else
1003
+		{
1004
+			// Ignoring Wildcard Emails?
1005
+			if (!empty($modSettings['sfs_wildcard_email']))
1006
+				$url .= '&nobadusername';
1007
+
1008
+			// Ignoring Wildcard Usernames?
1009
+			if (!empty($modSettings['sfs_wildcard_username']))
1010
+				$url .= '&nobademail';
1011
+
1012
+			// Ignoring Wildcard IPs?
1013
+			if (!empty($modSettings['sfs_wildcard_ip']))
1014
+				$url .= '&nobadip';
1015
+		}
1016
+
1017
+		// Tor handling, ignore them all.  Not recommended...
1018
+		if (!empty($modSettings['sfs_tor_check']) && $modSettings['sfs_tor_check'] == 1)
1019
+			$url .= '&notorexit';
1020
+		// Only block bad exit nodes.
1021
+		elseif (!empty($modSettings['sfs_tor_check']) && $modSettings['sfs_tor_check'] == 2)
1022
+			$url .= '&badtorexit';
1023
+		// Default handling for Tor is to block all exit nodes, nothing needed here.
1024
+
1025
+		return $url;
1026
+	}
1027
+
1028
+	/*
1029
+		Setup our possible SFS hosts.
1030
+		@resource: https://www.stopforumspam.com/usage
1031
+	*/
1032
+	public function sfsServerMapping($returnType = null)
1033
+	{
1034
+		global $txt;
1035
+
1036
+		// Global list of servers.
1037
+		$serverList = array(
1038
+			0 => array(
1039
+				'region' => 'global',
1040
+				'label' => $txt['sfs_region_global'],
1041
+				'host' => 'api.stopforumspam.org',
1042
+			),
1043
+			1 => array(
1044
+				'region' => 'us',
1045
+				'label' => $txt['sfs_region_us'],
1046
+				'host' => 'us.stopforumspam.org',
1047
+			),
1048
+			2 => array(
1049
+				'region' => 'eu',
1050
+				'label' => $txt['sfs_region_eu'],
1051
+				'host' => 'eruope.stopforumspam.org',
1052
+			),
1053
+		);
1054
+
1055
+		// Configs only need the labels.
1056
+		if ($returnType == 'config')
1057
+		{
1058
+			$temp = array();
1059
+			foreach ($serverList as $id_server => $server)
1060
+				$temp[$id_server] = $server['label'];
1061
+			return $temp;
1062
+		}
1063
+
1064
+		return $serverList;
1065
+	}
1066
+
1067
+	/*
1068
+		Verification Options
1069
+	*/
1070
+	private function getVerificationOptions()
1071
+	{
1072
+		global $user_info, $modSettings;
1073
+
1074
+		$optionsKey = $user_info['is_guest'] ? 'sfs_verification_options' : 'sfs_verification_options_members';
1075
+		$optionsKeyExtra = $user_info['is_guest'] ? 'sfs_verification_options_extra' : 'sfs_verification_options_membersextra';
1076
+
1077
+		// Standard options.
1078
+		if ($this->versionCheck('2.0', 'smf') && !empty($modSettings[$optionsKey]))
1079
+			$options = safe_unserialize($modSettings[$optionsKey]);
1080
+		elseif (!empty($modSettings[$optionsKey]))
1081
+			$options = $this->decodeJSON($modSettings[$optionsKey]);
1082
+		else
1083
+			$options = array();
1084
+
1085
+		// Extras.
1086
+		if (!empty($modSettings[$optionsKeyExtra])) 
1087
+		{
1088
+			$this->extraVerificationOptions = explode(',', $modSettings[$optionsKeyExtra]);
1089
+			$options = array_merge($options, $this->extraVerificationOptions);
1090
+		}
1091
+
1092
+		return $options;
1093
+	}
1094
+
1095
+	/*
1096
+		Defaults for SFS
1097
+		We don't specify all of them here, just what we need to make development easier.
1098
+	*/
1099
+	public function loadDefaults($undo = false)
1100
+	{
1101
+		global $modSettings;
1102
+
1103
+		// Specify the defaults, but only non empties.
1104
+		$defaultSettings = array(
1105
+			'sfs_enabled' => 1,
1106
+			'sfs_emailcheck' => 1,
1107
+			'sfs_region' => 0,
1108
+			'sfs_verification_options_members_post_threshold' => 5,
1109
+		);
1110
+
1111
+		// SMF 2.0 is serialized, SMF 2.1 is json.
1112
+		if ($this->versionCheck('2.0', 'smf'))
1113
+			$defaultSettings['sfs_verification_options'] = serialize(array('post'));
1114
+		else
1115
+			$defaultSettings['sfs_verification_options'] = json_encode(array('post'));
1116
+		
1117
+		// We undoing this? Maybe a save?
1118
+		if ($undo)
1119
+		{
1120
+			foreach ($this->changedSettings as $key => $value)
1121
+				unset($modSettings[$key], $this->changedSettings[$key]);
1122
+			return true;
1123
+		}
1124
+
1125
+		// Enabled settings.
1126
+		foreach ($defaultSettings as $key => $value)
1127
+			if (!isset($modSettings[$key]))
1128
+			{
1129
+				$this->changedSettings[$key] = null;
1130
+				$modSettings[$key] = $value;
1131
+			}
1132
+	}
1133
+
1134
+	/*
1135
+		Just a wrapper to tell defaults to undo.
1136
+	*/
1137
+	public function unloadDefaults()
1138
+	{
1139
+		return $this->loadDefaults(true);
1140
+	}
1141
+
1142
+	/*
1143
+		Global function to check version and software matches.
1144
+		@CalledIn: SMF 2.0, SMF 2.1
1145
+	*/
1146
+	public function versionCheck($version, $software = 'smf')
1147
+	{
1148
+		// We can't do this if the software doesn't match.
1149
+		if ($software !== $this->softwareName)
1150
+			return false;
1151
+
1152
+		// Allow multiple versions to pass.
1153
+		$version = (array) $version;
1154
+		foreach ($version as $v)
1155
+			if ($v == $this->softwareVersion)
1156
+				return true;
1157
+
1158
+		// No match? False.
1159
+		return false;
1160
+	}
1161
+
1162
+	/*
1163
+		Global loadLanguage function, should we want to split it out or need to load it differently
1164
+		@CalledIn: SMF 2.0, SMF 2.1
1165
+	*/
1166
+	public function loadLanguage()
1167
+	{
1168
+		// Load the langauge.
1169
+		loadLanguage('StopForumSpam');
1170
+	}
1171
+
1172
+	/*
1173
+		Handle searching for logs
1174
+	*/
1175
+	private function handleLogSearch()
1176
+	{
1177
+		global $context, $txt;
1178
+
1179
+		if (!empty($_REQUEST['params']) && empty($_REQUEST['is_search']))
1180
+		{
1181
+			$this->search_params = base64_decode(strtr($_REQUEST['params'], array(' ' => '+')));
1182
+			$this->search_params = $this->JSONDecode($this->search_params);
1183
+		}
1184
+
1185
+		$searchTypes = array(
1186
+			'member' => array('sql' => 'mem.real_name', 'label' => $txt['sfs_log_search_member']),
1187
+			'username' => array('sql' => 'l.username', 'label' => $txt['sfs_log_search_username']),
1188
+			'email' => array('sql' => 'l.email', 'label' => $txt['sfs_log_search_email']),
1189
+			'ip' => array('sql' => 'lm.ip', 'label' => $txt['sfs_log_search_ip']),
1190
+			'ip2' => array('sql' => 'lm.ip2', 'label' => $txt['sfs_log_search_ip2'])
1191
+		);
1192
+
1193
+		if (!isset($this->search_params['string']) || (!empty($_REQUEST['search']) && $this->search_params['string'] != $_REQUEST['search']))
1194
+			$this->search_params_string = empty($_REQUEST['search']) ? '' : $_REQUEST['search'];
1195
+		else
1196
+			$this->search_params_string = $this->search_params['string'];
1197
+
1198
+		if (isset($_REQUEST['search_type']) || empty($this->search_params['type']) || !isset($searchTypes[$this->search_params['type']]))
1199
+			$this->search_params_type = isset($_REQUEST['search_type']) && isset($searchTypes[$_REQUEST['search_type']]) ? $_REQUEST['search_type'] : (isset($searchTypes[$context['order']]) ? $context['order'] : 'member');
1200
+		else
1201
+			$this->search_params_type = $this->search_params['type'];
1202
+
1203
+		$this->search_params_column = $searchTypes[$this->search_params_type]['sql'];
1204
+		$this->search_params = array(
1205
+			'string' => $this->search_params_string,
1206
+			'type' => $this->search_params_type,
1207
+		);
1208
+
1209
+		// Setup the search context.
1210
+		$context['search_params'] = empty($this->search_params['string']) ? '' : base64_encode(json_encode($this->search_params));
1211
+		$context['search'] = array(
1212
+			'string' => $this->search_params['string'],
1213
+			'type' => $this->search_params['type'],
1214
+			'label' => $searchTypes[$this->search_params_type]['label'],
1215
+		);
1216
+
1217
+	}
1218
+}
0 1219
\ No newline at end of file
... ...
@@ -0,0 +1,115 @@
1
+<?php
2
+
3
+// If we have found SSI.php and we are outside of SMF, then we are running standalone.
4
+if (file_exists(dirname(__FILE__) . '/SSI.php') && !defined('SMF'))
5
+	require_once(dirname(__FILE__) . '/SSI.php');
6
+elseif (file_exists(getcwd() . '/SSI.php') && !defined('SMF'))
7
+	require_once(getcwd() . '/SSI.php');
8
+elseif (!defined('SMF')) // If we are outside SMF and can't find SSI.php, then throw an error
9
+	die('<b>Error:</b> Cannot install - please verify you put this file in the same place as SMF\'s SSI.php.');
10
+
11
+if (SMF == 'SSI')
12
+	db_extend('packages');
13
+
14
+$table = array(
15
+	'table_name' => '{db_prefix}log_sfs',
16
+	'columns' => array(
17
+		db_field('id_sfs', 'int', 0, true, true),
18
+		db_field('id_type', 'tinyint', 0),
19
+		db_field('log_time', 'int'),
20
+		db_field('id_member', 'mediumint'),
21
+		db_field('username', 'varchar', 255),
22
+		db_field('email', 'varchar', 255),
23
+		db_field('ip', 'varchar', 255),
24
+		db_field('ip2', 'varchar', 255),
25
+		db_field('checks', 'mediumtext'),
26
+		db_field('result', 'mediumtext'),
27
+	),
28
+	'indexes' => array(
29
+		array(
30
+			'columns' => array('id_sfs'),
31
+			'type' => 'primary',
32
+		),
33
+		array(
34
+			'columns' => array('id_type'),
35
+			'type' => 'index',
36
+		),
37
+	),
38
+	'if_exists' => 'ignore',
39
+	'error' => 'fatal',
40
+	'parameters' => array(),
41
+);
42
+
43
+$smcFunc['db_create_table']($table['table_name'], $table['columns'], $table['indexes'], $table['parameters'], $table['if_exists'], $table['error']);
44
+
45
+/*
46
+ * Calculates the proper settings to use in a column.
47
+ *
48
+ * @since 1.0
49
+*/
50
+function db_field($name, $type, $size = 0, $unsigned = true, $auto = false)
51
+{
52
+	$fields = array(
53
+		'varchar' => array(
54
+			'auto' => false,
55
+			'type' => 'varchar',
56
+			'size' => $size == 0 ? 50 : $size,
57
+			'null' => false,
58
+		),
59
+		'text' => array(
60
+			'auto' => false,
61
+			'type' => 'text',
62
+			'null' => false,
63
+		),
64
+		'mediumtext' => array(
65
+			'auto' => false,
66
+			'type' => 'mediumtext',
67
+			'null' => false,
68
+		),
69
+		'tinyint' => array(
70
+			'auto' => $auto,
71
+			'type' => 'tinyint',
72
+			'default' => 0,
73
+			'size' => empty($unsigned) ? 4 : 3,
74
+			'unsigned' => $unsigned,
75
+			'null' => false,
76
+		),
77
+		'smallint' => array(
78
+			'auto' => $auto,
79
+			'type' => 'smallint',
80
+			'default' => 0,
81
+			'size' => empty($unsigned) ? 6 : 5,
82
+			'unsigned' => $unsigned,
83
+			'null' => false,
84
+		),
85
+		'mediumint' => array(
86
+			'auto' => $auto,
87
+			'type' => 'mediumint',
88
+			'default' => 0,
89
+			'size' => 8,
90
+			'unsigned' => $unsigned,
91
+			'null' => false,
92
+		),
93
+		'int' => array(
94
+			'auto' => $auto,
95
+			'type' => 'int',
96
+			'default' => 0,
97
+			'size' => empty($unsigned) ? 11 : 10,
98
+			'unsigned' => $unsigned,
99
+			'null' => false,
100
+		),
101
+		'bigint' => array(
102
+			'auto' => $auto,
103
+			'type' => 'bigint',
104
+			'default' => 0,
105
+			'size' => 21,
106
+			'unsigned' => $unsigned,
107
+			'null' => false,
108
+		),
109
+	);
110
+
111
+	$field = $fields[$type];
112
+	$field['name'] = $name;
113
+
114
+	return $field;
115
+}
0 116
\ No newline at end of file
... ...
@@ -0,0 +1,29 @@
1
+<?xml version="1.0"?>
2
+<!DOCTYPE modification SYSTEM "http://www.simplemachines.org/xml/modification">
3
+<modification xmlns="http://www.simplemachines.org/xml/modification" xmlns:smf="http://www.simplemachines.org/">
4
+	<id>SleePy:StopForumSpam</id>
5
+	<version>1.0</version>
6
+
7
+	<file name="$sourcedir/Subs-Editor.php">
8
+		<operation>
9
+			<search position="replace"><![CDATA[	// Start with any testing.
10
+	if ($do_test)
11
+	{
12
+		// This cannot happen!
13
+		if (!isset($_SESSION[$verificationOptions['id'] . '_vv']['count']))
14
+			fatal_lang_error('no_access', false);
15
+]]></search>
16
+			<add><![CDATA[	// Start with any testing.
17
+	if ($do_test)
18
+	{
19
+		// Check against SFS
20
+		if (is_callable($smcFunc['classSFS'], 'checkRegisterPost'))
21
+			$smcFunc['classSFS']->checkVerificationTest($thisVerification, $verification_errors);
22
+
23
+		// This cannot happen!
24
+		if (!isset($_SESSION[$verificationOptions['id'] . '_vv']['count']))
25
+			fatal_lang_error('no_access', false);
26
+]]></add>
27
+		</operation>
28
+	</file>
29
+</modification>
0 30
\ No newline at end of file
... ...
@@ -0,0 +1,77 @@
1
+<?php
2
+
3
+/* The section Name */
4
+$txt['sfs_admin_area'] = 'Stop Forum Spam';
5
+$txt['sfs_admin_logs'] = 'SFS Logs';
6
+
7
+/* Admin Section General header */
8
+$txt['sfs_general_title'] = 'General Configuration';
9
+
10
+/* Admin section configuration options */
11
+$txt['sfs_enabled'] = 'Enable Stop Forum Spam?';
12
+$txt['sfs_log_debug'] = 'Enable Logging of all SFS requests (Debugging Only)?';
13
+$txt['sfs_ipcheck'] = 'Check IP Address?';
14
+$txt['sfs_usernamecheck'] = 'Check Username?';
15
+$txt['sfs_emailcheck'] = 'Check Email? (Recommended)';
16
+
17
+/* Admin section: Region Config */
18
+$txt['sfs_region'] = 'Geographic Access Region';
19
+$txt['sfs_region_global'] = 'Global (Recommended)';
20
+$txt['sfs_region_us'] = 'United States Region';
21
+$txt['sfs_region_eu'] = 'Europe Region';
22
+
23
+/* Admin section: Wildcard section */
24
+$txt['sfs_wildcard_email'] = 'Ignore Wildcard Email Checks';
25
+$txt['sfs_wildcard_username'] = 'Ignore Wildcard Username Checks';
26
+$txt['sfs_wildcard_ip'] = 'Ignore Wildcard IP Checks';
27
+
28
+/* Admin Section: Tor handling section */
29
+$txt['sfs_tor_check'] = 'TOR Exit Node Handling';
30
+$txt['sfs_tor_check_block'] = 'Block All Exit Nodes (Default)';
31
+$txt['sfs_tor_check_ignore'] = 'Ignore All Exit Nodes';
32
+$txt['sfs_tor_check_bad'] = 'Block Only Known Bad Exit Nodes';
33
+
34
+/* Admin Section: Verification Options header */
35
+$txt['sfs_verification_title'] = 'Verification Options';
36
+$txt['sfs_verification_desc'] = 'Usage of these options reuqire Anti-Spam Verification options to be setup and configurred.  Disabling verification options or not requiring it in specific sections will override these options.';
37
+
38
+/* Admin Section: Verification Options for guests */
39
+$txt['sfs_verification_options'] = 'Guest Verification Sections';
40
+$txt['sfs_verification_options_post'] = 'Posting';
41
+$txt['sfs_verification_options_report'] = 'Reporting Topics';
42
+$txt['sfs_verification_options_search'] = 'Search (Not Recommended)';
43
+$txt['sfs_verification_options_extra'] = 'Extra sections';
44
+$txt['sfs_verification_options_extra_subtext'] = 'Used for other mods or areas that add additional sections using custom verification names.  Use comma separted values. Use % for wildcards';
45
+
46
+$txt['sfs_verification_options_members'] = 'Member Verification Sections';
47
+$txt['sfs_verification_options_members_post_threshold'] = 'Post Count after which we stop these checks.';
48
+$txt['sfs_verification_options_membersextra'] = 'Extra sections';
49
+
50
+/* Request handling */
51
+$txt['sfs_request_failure'] = 'SFS Failed with invalid response';
52
+$txt['sfs_request_failure_nodata'] = 'SFS Failed as no data was sent';
53
+
54
+/* Spammer detection */
55
+$txt['sfs_request_blocked'] = 'Your request was denied as one or more emails, usernames, or IPs where found on the Stop Forum Spam blacklist';
56
+
57
+/* Admin Section Logs */
58
+$txt['sfs_log_no_entries_found'] = 'No Entries found in the SFS logs';
59
+$txt['sfs_log_search_member'] = 'Member';
60
+$txt['sfs_log_search_username'] = 'Username';
61
+$txt['sfs_log_search_email'] = 'Email';
62
+$txt['sfs_log_search_ip'] = 'IP Address';
63
+$txt['sfs_log_search_ip2'] = 'IP Address (Ban Check)';
64
+$txt['sfs_log_header_type'] = 'Log Type';
65
+$txt['sfs_log_header_time'] = 'Time';
66
+$txt['sfs_log_header_member'] = 'Member';
67
+$txt['sfs_log_header_username'] = 'Username';
68
+$txt['sfs_log_header_email'] = 'Email';
69
+$txt['sfs_log_header_ip'] = 'IP Address';
70
+$txt['sfs_log_header_ip2'] = 'IP Address (Ban Check)';
71
+$txt['sfs_log_checks'] = 'Checks';
72
+$txt['sfs_log_result'] = 'Results';
73
+$txt['sfs_log_search'] = 'Log Search';
74
+$txt['sfs_log_types_0'] = 'Debug';
75
+$txt['sfs_log_types_1'] = 'Username';
76
+$txt['sfs_log_types_2'] = 'Email';
77
+$txt['sfs_log_types_3'] = 'IP Address';
... ...
@@ -0,0 +1,76 @@
1
+<?xml version="1.0"?>
2
+<!DOCTYPE package-info SYSTEM "http://www.simplemachines.org/xml/package-info">
3
+<package-info xmlns="http://www.simplemachines.org/xml/package-info" xmlns:smf="http://www.simplemachines.org/">
4
+	<id>SleePy:StopForumSpam</id>
5
+	<name>Stop Forum Spam</name>
6
+	<version>1.0</version>
7
+	<type>modification</type>
8
+
9
+	<install for="1.1.*">
10
+		<readme lang="english" parsebbc="true" type="inline">This mod is [b]not compatible[/b] with your version of SMF, it requires 2.0 or later.</readme>
11
+	</install>
12
+
13
+	<!-- 2.0 has no support for hooks -->
14
+	<install for="2.0.*">
15
+		<database>install_sfs.php</database>
16
+		<code type="file">sfs_hooks_install.php</code>
17
+		<modification>install_smf20.xml</modification>
18
+
19
+		<require-file name="language/StopForumSpam.english.php" destination="$themes_dir/default/languages" />
20
+		<require-file name="StopForumSpam.php" destination="$sourcedir" />
21
+
22
+		<redirect url="?action=admin;area=modsettings;sa=sfs" />
23
+	</install>
24
+
25
+	<uninstall for="2.0.*">
26
+		<!-- database changes, undone -->
27
+		<database>install_sfs.php</database>
28
+		<code type="file">sfs_hooks_remove.php</code>
29
+
30
+		<modification reverse="true">install_smf20.xml</modification>
31
+
32
+		<!-- language files, removed -->
33
+		<remove-dir name="$themes_dir/default/languages/StopForumSpam.english.php" />
34
+
35
+		<!-- source files, removed -->
36
+		<remove-dir name="$sourcedir/StopForumSpam.php" />
37
+	</uninstall>
38
+
39
+	<install for="2.1 RC3, 2.1.*">
40
+		<database>install_sfs.php</database>
41
+
42
+		<require-file name="language/StopForumSpam.english.php" destination="$themes_dir/default/languages" />
43
+		<require-file name="StopForumSpam.php" destination="$sourcedir" />
44
+
45
+		<!-- All the hooks -->
46
+		<hook hook="integrate_pre_include" function="$sourcedir/StopForumSpam.php" />
47
+		<hook hook="integrate_pre_load" function="SFS::hook_pre_load" />
48
+		<hook hook="integrate_admin_areas" function="SFS::hook_admin_areas" />
49
+		<hook hook="integrate_modify_modifications" function="SFS::hook_modify_modifications" />
50
+		<hook hook="integrate_register" function="SFS::hook_register" />
51
+		<hook hook="integrate_manage_logs" function="SFS::hook_manage_logs" />
52
+
53
+		<redirect url="?action=admin;area=securitysettings;sa=sfs" />
54
+	</install>
55
+
56
+	<uninstall for="2.1 RC3, 2.1.*">
57
+		<!-- database changes, undone -->
58
+		<database>install_sfs.php</database>
59
+		<code type="file">uninstall-sd-required.php</code>
60
+
61
+		<!-- All the hooks, removed -->
62
+		<hook hook="integrate_pre_include" function="$sourcedir/StopForumSpam.php" reverse="true" />
63
+		<hook hook="integrate_pre_load" function="SFS::hook_pre_load" reverse="true" />
64
+		<hook hook="integrate_admin_areas" function="SFS::hook_admin_areas" reverse="true" />
65
+		<hook hook="integrate_modify_modifications" function="SFS::hook_modify_modifications" reverse="true" />
66
+		<hook hook="integrate_register" function="SFS::hook_register" reverse="true" />
67
+		<hook hook="integrate_manage_logs" function="SFS::hook_manage_logs" reverse="true" />
68
+
69
+		<!-- language files, removed -->
70
+		<remove-dir name="$themes_dir/default/languages/StopForumSpam.english.php" />
71
+
72
+		<!-- source files, removed -->
73
+		<remove-dir name="$sourcedir/StopForumSpam.php" />
74
+	</uninstall>
75
+
76
+</package-info>
0 77
\ No newline at end of file
... ...
@@ -0,0 +1,24 @@
1
+<?php
2
+
3
+// If we have found SSI.php and we are outside of SMF, then we are running standalone.
4
+if (file_exists(dirname(__FILE__) . '/SSI.php') && !defined('SMF'))
5
+	require_once(dirname(__FILE__) . '/SSI.php');
6
+elseif (file_exists(getcwd() . '/SSI.php') && !defined('SMF'))
7
+	require_once(getcwd() . '/SSI.php');
8
+elseif (!defined('SMF')) // If we are outside SMF and can't find SSI.php, then throw an error
9
+	die('<b>Error:</b> Cannot install - please verify you put this file in the same place as SMF\'s SSI.php.');
10
+
11
+if (SMF == 'SSI')
12
+	db_extend('packages');
13
+
14
+$hooks = array(
15
+	'integrate_pre_include' => '$sourcedir/StopForumSpam.php',
16
+	'integrate_pre_load' => 'SFS::hook_pre_load',
17
+	'integrate_admin_areas' => 'SFS::hook_admin_areas',
18
+	'integrate_modify_modifications' => 'SFS::hook_modify_modifications',
19
+	'integrate_register' => 'SFS::hook_register',
20
+	'integrate_manage_logs' => 'SFS::hook_manage_logs'
21
+);
22
+
23
+foreach ($hooks as $hook => $func)
24
+	add_integration_function($hook, $func, true);
0 25
\ No newline at end of file
... ...
@@ -0,0 +1,24 @@
1
+<?php
2
+
3
+// If we have found SSI.php and we are outside of SMF, then we are running standalone.
4
+if (file_exists(dirname(__FILE__) . '/SSI.php') && !defined('SMF'))
5
+	require_once(dirname(__FILE__) . '/SSI.php');
6
+elseif (file_exists(getcwd() . '/SSI.php') && !defined('SMF'))
7
+	require_once(getcwd() . '/SSI.php');
8
+elseif (!defined('SMF')) // If we are outside SMF and can't find SSI.php, then throw an error
9
+	die('<b>Error:</b> Cannot install - please verify you put this file in the same place as SMF\'s SSI.php.');
10
+
11
+if (SMF == 'SSI')
12
+	db_extend('packages');
13
+
14
+$hooks = array(
15
+	'integrate_pre_include' => '$sourcedir/StopForumSpam.php',
16
+	'integrate_pre_load' => 'SFS::hook_pre_load',
17
+	'integrate_admin_areas' => 'SFS::hook_admin_areas',
18
+	'integrate_modify_modifications' => 'SFS::hook_modify_modifications',
19
+	'integrate_register' => 'SFS::hook_register',
20
+	'integrate_manage_logs' => 'SFS::hook_manage_logs'
21
+);
22
+
23
+foreach ($hooks as $hook => $func)
24
+	remove_integration_function($hook, $func);
0 25
\ No newline at end of file
1 26