WordPress Zero Spam - Version 4.10.0

Version Description

  • Enhancement - Various performance improvements & caching added.
  • Enhancement - Easier to use class methods for integrating Zero Spam into any plugin or theme.
  • Enhancement - New WordPress_Zero_Spam class added.
  • Enhancement - IPs are now checked against known, safe hosts and user agents (i.e. search engine crawlers).
  • Enhancement - Added advanced debugging functionality.
Download this release

Release Info

Developer bmarshall511
Plugin Icon 128x128 WordPress Zero Spam
Version 4.10.0
Comparing to
See all releases

Code changes from version 4.9.13 to 4.10.0

assets/css/debug.css ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .wpzerospam-debug-overlay {
2
+ background: rgba(255, 255, 255, 0.9);
3
+ bottom: 0;
4
+ font-size: 13px;
5
+ left: 0;
6
+ max-height: 95%;
7
+ max-width: 800px;
8
+ overflow: auto;
9
+ padding: 2em;
10
+ position: fixed;
11
+ width: 100%;
12
+ }
13
+
14
+ .wpzerospam-debug-item {
15
+ display: flex;
16
+ }
17
+
18
+ .wpzerospam-debug-item-label {
19
+ flex-shrink: 0;
20
+ width: 16em;
21
+ }
22
+
23
+ .wpzerospam-debug-item-value {
24
+ flex-grow: 1;
25
+ }
classes/class-wordpress-zero-spam.php ADDED
@@ -0,0 +1,931 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WordPress Zero Spam class.
4
+ *
5
+ * @package WordPressZeroSpam
6
+ */
7
+
8
+ // Security Note: Blocks direct access to the plugin PHP files.
9
+ defined( 'ABSPATH' ) || die();
10
+
11
+ /**
12
+ * WordPress Zero Spam class.
13
+ */
14
+ class WordPress_Zero_Spam {
15
+ /**
16
+ * Contains all plugin options.
17
+ *
18
+ * @var array Array of plugin options.
19
+ */
20
+ public $options;
21
+
22
+ /**
23
+ * The default core plugin options.
24
+ *
25
+ * @var array Array of core plugin options.
26
+ */
27
+ public $default_options = array(
28
+ 'debug' => 'disabled',
29
+ 'ip_whitelist' => '',
30
+ 'cookie_expiration' => 7,
31
+ 'log_spam' => false,
32
+ 'log_blocked_ips' => false,
33
+ 'share_detections' => true,
34
+ 'stopforumspam_confidence_min' => 20,
35
+ 'botscout_count_min' => 5,
36
+ 'botscout_api' => false,
37
+ 'api_timeout' => 5,
38
+ 'block_handler' => 403,
39
+ 'blocked_redirect_url' => 'https://google.com',
40
+ 'blocked_message' => false,
41
+ 'ipstack_api' => false,
42
+ );
43
+
44
+ /**
45
+ * The current user's IP address.
46
+ *
47
+ * @var string The current user's IP address.
48
+ */
49
+ public $current_user_ip;
50
+
51
+ /**
52
+ * The current user's IP address access.
53
+ *
54
+ * @var array IP address access details.
55
+ */
56
+ public $current_user_ip_access;
57
+
58
+ /**
59
+ * Database tables.
60
+ *
61
+ * @var array Array of plugin database tables.
62
+ */
63
+ public $tables;
64
+
65
+ /**
66
+ * Class constructor.
67
+ */
68
+ public function __construct() {
69
+ // Triggered on the WP init action.
70
+ add_action( 'init', array( $this, 'wp_init' ) );
71
+
72
+ // Triggered on the WP wp_footer action.
73
+ add_action( 'wp_footer', array( $this, 'wp_footer' ) );
74
+
75
+ // Handles IPs that have been denied access.
76
+ add_action( 'template_redirect', array( $this, 'access_check' ) );
77
+ }
78
+
79
+ /**
80
+ * Checks is an IP is safe (i.e. from a known bot or crawler)
81
+ */
82
+ public function is_known_safe_ip() {
83
+ $safe_hosts = array(
84
+ 'googlebot.com',
85
+ 'google.com',
86
+ 'search.msn.com',
87
+ 'bing.com',
88
+ 'yahoo.com',
89
+ 'duckduckgo.com',
90
+ 'baidu.com',
91
+ 'yandex.com',
92
+ 'exabot.com',
93
+ 'facebook.com',
94
+ 'alexa.com',
95
+ );
96
+
97
+ $safe_user_agents = array(
98
+ 'Googlebot',
99
+ 'Bingbot',
100
+ 'Slurp',
101
+ 'DuckDuckBot',
102
+ 'Baiduspider',
103
+ 'YandexBot',
104
+ 'facebot',
105
+ 'ia_archiver',
106
+ );
107
+
108
+ $ip_host = gethostbyaddr( $this->current_user_ip );
109
+ $user_agent = ! empty( $_SERVER['HTTP_USER_AGENT'] ) ? esc_html( $_SERVER['HTTP_USER_AGENT'] ) : false;
110
+
111
+ if ( ! $ip_host || ! $user_agent ) {
112
+ return false;
113
+ }
114
+
115
+ // 1. Check hostnames.
116
+ foreach ( $safe_hosts as $key => $host ) {
117
+ if ( stripos( $ip_host, $host ) !== false ) {
118
+ return true;
119
+ }
120
+ }
121
+
122
+ // 2. Check user agents.
123
+ foreach ( $safe_user_agents as $key => $agent ) {
124
+ if ( stripos( $user_agent, $agent ) !== false ) {
125
+ return true;
126
+ }
127
+ }
128
+
129
+ return false;
130
+ }
131
+
132
+ /**
133
+ * Checks an IP access.
134
+ *
135
+ * @param string $ip The IP address to check.
136
+ */
137
+ public function get_access( $ip ) {
138
+ $access = array(
139
+ 'ip_checked' => $ip,
140
+ 'has_access' => true,
141
+ 'access_checked' => false,
142
+ 'cached' => false,
143
+ 'blacklist_api' => false,
144
+ 'attempts' => false,
145
+ );
146
+
147
+ // Ignore logged in users.
148
+ if ( is_user_logged_in() ) {
149
+ $access['access_checked'] = 'authenticated';
150
+ return $this->set_access_cookies( $access );
151
+ }
152
+
153
+ // 1. Check if an access check has already been ran for this IP.
154
+ if ( $this->get_cookie( 'access_checked' ) && $this->get_cookie( 'ip_checked' ) === $ip ) {
155
+ // IP has already been checked, return the saved access.
156
+ foreach ( $access as $key => $value ) {
157
+ $access[ $key ] = $this->get_cookie( $key );
158
+ }
159
+
160
+ $access['cached'] = 'cookie';
161
+
162
+ return $this->set_access_cookies( $access );
163
+ }
164
+
165
+ // 2. Check known/safe hosts (i.e. Google crawlers, etc.)
166
+ if ( $this->is_known_safe_ip( $ip ) ) {
167
+ $access['access_checked'] = 'safe_ip';
168
+
169
+ return $this->set_access_cookies( $access );
170
+ }
171
+
172
+ // 3. Check the whitelisted IP addresses.
173
+ $whitelisted_ips = $this->get_whitelisted_ips();
174
+
175
+ if ( array_key_exists( $ip, $whitelisted_ips ) ) {
176
+ // IP address found in the whitelist.
177
+ $access['access_checked'] = 'whitelisted';
178
+
179
+ return $this->set_access_cookies( $access );
180
+ }
181
+
182
+ // 4. Check if IP has been blocked.
183
+ $blocked_ip = $this->get_blocked_ip( $ip );
184
+ if ( $blocked_ip ) {
185
+ $access['attempts'] = $blocked_ip['attempts'];
186
+
187
+ // IP has been blocked, check the type.
188
+ if ( 'permanent' === $blocked_ip['blocked_type'] ) {
189
+ // Permanent block.
190
+ $access['access_checked'] = 'permanent_block';
191
+ $access['has_access'] = false;
192
+ return $this->set_access_cookies( $access );
193
+ } else {
194
+ // Temporary block, check if still valid.
195
+ if ( $this->is_blocked_ip_active( $blocked_ip ) ) {
196
+ $access['access_checked'] = 'temporary_block';
197
+ $access['has_access'] = false;
198
+ return $this->set_access_cookies( $access );
199
+ }
200
+ }
201
+ }
202
+
203
+ // 5. Check if the IP appears on the blacklist.
204
+ $blacklisted_ip = $this->get_blacklisted_ip( $ip );
205
+ if ( $blacklisted_ip ) {
206
+ // IP has been blacklisted, check if it needs updated.
207
+ $current_datetime = strtotime( current_time( 'mysql' ) );
208
+ $last_updated = strtotime( $blacklisted_ip['last_updated'] );
209
+ if ( $current_datetime >= ( $last_updated + MONTH_IN_SECONDS ) ) {
210
+ // Expired, update the record.
211
+ $blacklisted_api_ip = $this->get_ip_from_api( $ip, $blacklisted_ip['blacklist_service'] );
212
+ if ( ! $blacklisted_api_ip ) {
213
+ // IP not found, delete.
214
+ $this->delete_blacklisted_ip( $ip );
215
+ } else {
216
+ // IP found, update (or delete depending on plugin settings) it.
217
+ if ( $this->update_blacklisted_ip( $blacklisted_api_ip, 'update' ) ) {
218
+ $access['has_access'] = false;
219
+ $access['access_checked'] = 'blacklist';
220
+ $access['blacklist_api'] = $blacklisted_ip['blacklist_service'];
221
+ $access['attempts'] = $blacklisted_ip['attempts'];
222
+
223
+ return $this->set_access_cookies( $access );
224
+ }
225
+ }
226
+ } else {
227
+ // Not expired.
228
+ $access['has_access'] = false;
229
+ $access['access_checked'] = 'blacklist';
230
+ $access['blacklist_api'] = $blacklisted_ip['blacklist_service'];
231
+ $access['attempts'] = $blacklisted_ip['attempts'];
232
+
233
+ return $this->set_access_cookies( $access );
234
+ }
235
+ }
236
+
237
+ // 6. Check the IP against the StopForumSpam API.
238
+ $blacklisted_api_ip = $this->get_ip_from_api( $ip, 'stopforumspam' );
239
+ if ( $blacklisted_api_ip && $this->update_blacklisted_ip( $blacklisted_api_ip, 'insert' ) ) {
240
+ $access['has_access'] = false;
241
+ $access['access_checked'] = 'blacklist';
242
+ $access['blacklist_api'] = 'stopforumspam';
243
+ $access['attempts'] = $blacklisted_api_ip['attempts'];
244
+
245
+ return $this->set_access_cookies( $access );
246
+ }
247
+
248
+ // 7. Check the IP against the BotScout API.
249
+ $blacklisted_api_ip = $this->get_ip_from_api( $ip, 'botscout' );
250
+ if ( $blacklisted_api_ip && $this->update_blacklisted_ip( $blacklisted_api_ip, 'insert' ) ) {
251
+ $access['has_access'] = false;
252
+ $access['access_checked'] = 'blacklist';
253
+ $access['blacklist_api'] = 'botscout';
254
+ $access['attempts'] = $blacklisted_api_ip['attempts'];
255
+
256
+ return $this->set_access_cookies( $access );
257
+ }
258
+
259
+ return $this->set_access_cookies( $access );
260
+ }
261
+
262
+ /**
263
+ * Handles IPs that have been denied access.
264
+ */
265
+ public function access_check() {
266
+ global $wpdb;
267
+
268
+ if ( ! $this->current_user_ip_access['has_access'] ) {
269
+ $block_type = $this->current_user_ip_access['access_checked'];
270
+ $log_data = array();
271
+
272
+ // IP doesn't have access, update attempts.
273
+ switch ( $block_type ) {
274
+ case 'temporary_block':
275
+ case 'permanent_block':
276
+ case 'blacklist':
277
+ $log_data['reason'] = $block_type;
278
+
279
+ switch ( $block_type ) {
280
+ case 'temporary_block':
281
+ case 'permanent_block':
282
+ $table = 'blocked';
283
+ break;
284
+ case 'blacklist':
285
+ $table = 'blacklist';
286
+ break;
287
+ }
288
+
289
+ if ( ! $this->current_user_ip_access[ 'attempts' ] ) {
290
+ $this->current_user_ip_access[ 'attempts' ] = 1;
291
+ } else {
292
+ $this->current_user_ip_access[ 'attempts' ]++;
293
+ }
294
+
295
+ $wpdb->update(
296
+ $this->tables[ $table ],
297
+ array(
298
+ 'attempts' => $this->current_user_ip_access[ 'attempts' ],
299
+ ),
300
+ array(
301
+ 'user_ip' => $this->current_user_ip,
302
+ )
303
+ );
304
+
305
+ $this->set_cookie( 'attempts', $this->current_user_ip_access[ 'attempts' ], 0 );
306
+ break;
307
+ }
308
+
309
+ // Log the detection.
310
+ $this->log_detection( 'blocked', $log_data );
311
+
312
+ if ( 'redirect' === $this->options['block_handler'] ) {
313
+ wp_redirect( esc_url( $this->options['blocked_redirect_url'] ) );
314
+ exit();
315
+ } else {
316
+ status_header( 403 );
317
+ die( $this->options['blocked_message'] );
318
+ }
319
+ }
320
+ }
321
+
322
+ /**
323
+ * Logs a IP detections.
324
+ */
325
+ public function log_detection( $type, $data ) {
326
+ global $wpdb;
327
+
328
+ $record = array(
329
+ 'user_ip' => $this->current_user_ip,
330
+ 'log_type' => $type,
331
+ 'date_recorded' => current_time( 'mysql' ),
332
+ );
333
+
334
+
335
+ // If sharing detections is enabled, send the detection to Zero Spam.
336
+ if ( 'enabled' === $this->options['share_detections'] ) {
337
+ $this->share_detection( $record['user_ip'], $record['log_type'] );
338
+ }
339
+
340
+ // Check if logging detections & 'blocks' are enabled.
341
+ if (
342
+ 'enabled' !== $this->options['log_spam'] ||
343
+ ( 'blocked' === $record['log_type'] && 'enabled' !== $this->options['log_blocked_ips'] )
344
+ ) {
345
+ // Logging disabled.
346
+ return false;
347
+ }
348
+
349
+ // Logging enabled, get the current URL & IP location information.
350
+ $location = $this->get_ip_geolocation( $record['user_ip'] );
351
+ $current_url = $this->get_current_url();
352
+
353
+ // Add additional information to the detection record.
354
+ $record['page_url'] = $current_url;
355
+ $record['submission_data'] = wp_json_encode( $data );
356
+
357
+ if ( $location ) {
358
+ $record['country'] = ! empty( $location['country_code'] ) ? $location['country_code'] : false;
359
+ $record['region'] = ! empty( $location['region_code'] ) ? $location['region_code'] : false;
360
+ $record['city'] = ! empty( $location['city'] ) ? $location['city'] : false;
361
+ $record['latitude'] = ! empty( $location['latitude'] ) ? $location['latitude'] : false;
362
+ $record['longitude'] = ! empty( $location['longitude'] ) ? $location['longitude'] : false;
363
+ }
364
+
365
+ return $wpdb->insert( $this->tables['log'], $record );
366
+ }
367
+
368
+ /**
369
+ * Returns the current URL.
370
+ */
371
+ public function get_current_url() {
372
+ global $wp;
373
+
374
+ return home_url( add_query_arg( array(), $wp->request ) );
375
+ }
376
+
377
+ /**
378
+ * Retreives an IP geolocation.
379
+ */
380
+ public function get_ip_geolocation( $ip ) {
381
+ if ( empty( $this->options['ipstack_api'] ) ) {
382
+ return false;
383
+ }
384
+
385
+ $base_url = 'http://api.ipstack.com/';
386
+ $remote_url = $base_url . $ip . '?access_key=' . $this->options['ipstack_api'];
387
+ $response = wp_remote_get( $remote_url, array( 'timeout' => $this->options['api_timeout'] ) );
388
+
389
+ if ( is_array( $response ) && ! is_wp_error( $response ) ) {
390
+ $info = json_decode( $response['body'], true );
391
+
392
+ return array(
393
+ 'type' => ! empty( $info['type'] ) ? sanitize_text_field( $info['type'] ) : false,
394
+ 'continent_code' => ! empty( $info['continent_code'] ) ? sanitize_text_field( $info['continent_code'] ) : false,
395
+ 'continent_name' => ! empty( $info['continent_name'] ) ? sanitize_text_field( $info['continent_name'] ) : false,
396
+ 'country_code' => ! empty( $info['country_code'] ) ? sanitize_text_field( $info['country_code'] ) : false,
397
+ 'country_name' => ! empty( $info['country_name'] ) ? sanitize_text_field( $info['country_name'] ) : false,
398
+ 'region_code' => ! empty( $info['region_code'] ) ? sanitize_text_field( $info['region_code'] ) : false,
399
+ 'region_name' => ! empty( $info['region_name'] ) ? sanitize_text_field( $info['region_name'] ) : false,
400
+ 'city' => ! empty( $info['city'] ) ? sanitize_text_field( $info['city'] ) : false,
401
+ 'zip' => ! empty( $info['zip'] ) ? sanitize_text_field( $info['zip'] ) : false,
402
+ 'latitude' => ! empty( $info['latitude'] ) ? sanitize_text_field( $info['latitude'] ) : false,
403
+ 'longitude' => ! empty( $info['longitude'] ) ? sanitize_text_field( $info['longitude'] ) : false,
404
+ 'flag' => ! empty( $info['location']['country_flag'] ) ? sanitize_text_field( $info['location']['country_flag'] ) : false,
405
+ );
406
+ }
407
+
408
+ return false;
409
+ }
410
+
411
+ /**
412
+ * Share a detection with Zero Spam.
413
+ */
414
+ public function share_detection( $ip, $type ) {
415
+ // The Zero Spam API endpoint for sharing detections.
416
+ $api_url = 'https://zerospam.org/wp-json/wpzerospamapi/v1/detection/';
417
+
418
+ // Setup the request parameters.
419
+ $request_args = array(
420
+ 'method' => 'POST',
421
+ 'body' => array(
422
+ 'ip' => $ip,
423
+ 'type' => $type,
424
+ 'site' => site_url(),
425
+ 'email' => get_bloginfo( 'admin_email' ),
426
+ 'wpversion' => get_bloginfo( 'version' ),
427
+ 'name' => get_bloginfo( 'name' ),
428
+ 'desc' => get_bloginfo( 'description' ),
429
+ 'language' => get_bloginfo( 'language' ),
430
+ 'version' => WORDPRESS_ZERO_SPAM_VERSION,
431
+ ),
432
+ 'sslverify' => true,
433
+ );
434
+
435
+ // For debugging purposes only.
436
+ if ( WP_DEBUG ) {
437
+ $request_args['sslverify'] = false;
438
+ }
439
+
440
+ // Send the request.
441
+ $request = wp_remote_post( $api_url, $request_args );
442
+ if ( is_wp_error( $request ) ) {
443
+ // Request failed.
444
+ return false;
445
+ }
446
+
447
+ // Request succeeded, return the result.
448
+ return wp_remote_retrieve_body( $request );
449
+ }
450
+
451
+ /**
452
+ * Triggered on the WP init action.
453
+ */
454
+ public function wp_footer() {
455
+ // Display debug info if enabled.
456
+ if ( 'enabled' === $this->options['debug'] ) {
457
+ wp_enqueue_style(
458
+ 'wpzerospam-debug',
459
+ plugin_dir_url( WORDPRESS_ZERO_SPAM ) .
460
+ 'assets/css/debug.css',
461
+ false,
462
+ WORDPRESS_ZERO_SPAM_VERSION
463
+ );
464
+ ?>
465
+ <div class="wpzerospam-debug-overlay">
466
+ <div class="wpzerospam-debug-item">
467
+ <div class="wpzerospam-debug-item-label"><strong><?php esc_html_e( 'Options', 'wpzerospam' ); ?>:</strong></div>
468
+ <div class="wpzerospam-debug-item-value">
469
+ <?php foreach ( $this->options as $key => $value ): ?>
470
+ <div class="wpzerospam-debug-item">
471
+ <div class="wpzerospam-debug-item-label"><?php esc_html_e( $key ); ?></div>
472
+ <div class="wpzerospam-debug-item-value"><?php esc_html_e( $value ); ?></div>
473
+ </div>
474
+ <?php endforeach; ?>
475
+ </div>
476
+ </div>
477
+ <div class="wpzerospam-debug-item">
478
+ <div class="wpzerospam-debug-item-label"><strong><?php esc_html_e( 'Current User IP', 'wpzerospam' ); ?>:</strong></div>
479
+ <div class="wpzerospam-debug-item-value"><?php echo esc_html( $this->current_user_ip ); ?></div>
480
+ </div>
481
+ <div class="wpzerospam-debug-item">
482
+ <div class="wpzerospam-debug-item-label"><strong><?php esc_html_e( 'Current User IP Access', 'wpzerospam' ); ?>:</strong></div>
483
+ <div class="wpzerospam-debug-item-value">
484
+ <?php foreach ( $this->current_user_ip_access as $key => $value ): ?>
485
+ <div class="wpzerospam-debug-item">
486
+ <div class="wpzerospam-debug-item-label"><?php esc_html_e( $key ); ?></div>
487
+ <div class="wpzerospam-debug-item-value"><?php esc_html_e( $value ); ?></div>
488
+ </div>
489
+ <?php endforeach; ?>
490
+ </div>
491
+ </div>
492
+ </div>
493
+ <?php
494
+ }
495
+ }
496
+
497
+ /**
498
+ * Triggered on the WP init action.
499
+ */
500
+ public function wp_init() {
501
+ global $wpdb;
502
+
503
+ // Set the database tables.
504
+ $this->tables = array(
505
+ 'log' => $wpdb->prefix . 'wpzerospam_log',
506
+ 'blocked' => $wpdb->prefix . 'wpzerospam_blocked',
507
+ 'blacklist' => $wpdb->prefix . 'wpzerospam_blacklist',
508
+ );
509
+
510
+ // Set the plugin options.
511
+ $this->options = $this->get_options();
512
+ if ( empty( $this->options['blocked_message'] ) ) {
513
+ $this->options['blocked_message'] = __( 'You have been blocked from visiting this site by WordPress Zero Spam due to detected spam activity.', 'wpzerospam' );
514
+ }
515
+
516
+ // Set the current user's IP address.
517
+ if ( 'enabled' === $this->options['debug'] && ! empty( $_REQUEST['wpzerospamip'] ) ) {
518
+ $this->current_user_ip = $_REQUEST['wpzerospamip'];
519
+ } else {
520
+ $this->current_user_ip = $this->get_user_ip();
521
+ }
522
+
523
+ // Set the current user's IP address access.
524
+ $this->current_user_ip_access = $this->get_access( $this->current_user_ip );
525
+ }
526
+
527
+ /**
528
+ * Returns the saved plugin options.
529
+ */
530
+ public function get_options() {
531
+ $options = $this->default_options;
532
+ $options = array_merge( $options, get_option( 'wpzerospam' ) );
533
+ $options = apply_filters( 'wpzerospam_options', $options );
534
+
535
+ return $options;
536
+ }
537
+
538
+ /**
539
+ * Sets access cookies.
540
+ *
541
+ * @param array $access Access details.
542
+ */
543
+ public function set_access_cookies( $access ) {
544
+ foreach ( $access as $key => $value ) {
545
+ $this->set_cookie( $key, $value, 0 );
546
+ }
547
+
548
+ return $access;
549
+ }
550
+
551
+ /**
552
+ * Updates a blacklisted IP.
553
+ *
554
+ * @param array $api_data The returned IP data from the API.
555
+ */
556
+ public function update_blacklisted_ip( $api_data, $type ) {
557
+ global $wpdb;
558
+
559
+ if ( ! $api_data ) {
560
+ return false;
561
+ }
562
+
563
+ switch ( $api_data['api'] ) {
564
+ case 'stopforumspam':
565
+ if ( ! empty( $api_data['confidence'] ) && $api_data['confidence'] < $this->options['stopforumspam_confidence_min'] ) {
566
+ // Doesn't meet the threshold, delete the IP from the database.
567
+ $this->delete_blacklisted_ip( $api_data['ip_address'] );
568
+ return false;
569
+ }
570
+ break;
571
+ case 'botscout':
572
+ if ( ! empty( $api_data['count'] ) && $api_data['count'] < $this->options['botscout_count_min'] ) {
573
+ // Doesn't meet the threshold, delete the IP from the database.
574
+ $this->delete_blacklisted_ip( $api_data['ip_address'] );
575
+ return false;
576
+ }
577
+ break;
578
+ }
579
+
580
+ // Update the record.
581
+ if ( 'update' === $type ) {
582
+ $wpdb->update(
583
+ $this->tables['blacklist'],
584
+ array(
585
+ 'last_updated' => current_time( 'mysql' ),
586
+ 'blacklist_data' => wp_json_encode( $api_data ),
587
+ ),
588
+ array(
589
+ 'user_ip' => $api_data['ip_address'],
590
+ )
591
+ );
592
+ } else {
593
+ $wpdb->insert(
594
+ $this->tables['blacklist'],
595
+ array(
596
+ 'user_ip' => $api_data['ip_address'],
597
+ 'last_updated' => current_time( 'mysql' ),
598
+ 'blacklist_service' => $api_data['api'],
599
+ 'blacklist_data' => wp_json_encode( $api_data ),
600
+ )
601
+ );
602
+ }
603
+
604
+ return true;
605
+ }
606
+
607
+ /**
608
+ * Deletes an IP from the blacklist.
609
+ *
610
+ * @param string $ip The IP address to delete.
611
+ */
612
+ public function delete_blacklisted_ip( $ip ) {
613
+ global $wpdb;
614
+
615
+ $wpdb->delete(
616
+ $this->tables['blacklist'],
617
+ array(
618
+ 'user_ip' => $ip
619
+ )
620
+ );
621
+ }
622
+
623
+ /**
624
+ * Gets an IP from an API.
625
+ *
626
+ * @param string $ip IP address to query.
627
+ * @param string $api The API to query.
628
+ */
629
+ public function get_ip_from_api( $ip, $api ) {
630
+ $cache_key = sanitize_title( $api . '_' . $ip );
631
+ $data = wp_cache_get( $cache_key );
632
+
633
+ if ( false === $data ) {
634
+ switch ( $api ) {
635
+ case 'stopforumspam':
636
+ $api_url = 'https://api.stopforumspam.org/api?';
637
+ $params = array(
638
+ 'ip' => $ip,
639
+ 'json' => '',
640
+ );
641
+ break;
642
+ case 'botscout':
643
+ $api_url = 'https://botscout.com/test/?';
644
+ $params = array(
645
+ 'ip' => $ip,
646
+ 'key' => $this->options['botscout_api'],
647
+ );
648
+ break;
649
+ }
650
+
651
+ if ( ! empty( $api_url ) ) {
652
+ $endpoint = $api_url . http_build_query( $params );
653
+ $response = wp_remote_get( $endpoint, array( 'timeout' => $this->options['api_timeout'] ) );
654
+ if ( is_array( $response ) && ! is_wp_error( $response ) ) {
655
+ $body_data = wp_remote_retrieve_body( $response );
656
+ switch ( $api ) {
657
+ case 'stopforumspam':
658
+ $body_data = json_decode( $body_data, true );
659
+ if (
660
+ ! empty( $body_data['success'] ) &&
661
+ $body_data['success'] &&
662
+ ! empty( $body_data['ip'] ) &&
663
+ ! empty( $body_data['ip']['appears'] )
664
+ ) {
665
+ $data = $body_data['ip'];
666
+ $data['api'] = 'stopforumspam';
667
+ $data['ip_address'] = $ip;
668
+ }
669
+ break;
670
+ case 'botscout':
671
+ if ( strpos( $body_data, '!' ) === false ) {
672
+ list( $matched, $type, $count ) = explode( '|', $body_data );
673
+ if ( 'Y' === $matched ) {
674
+ $data = array(
675
+ 'type' => $type,
676
+ 'count' => $count,
677
+ 'api' => 'botscout',
678
+ 'ip_address' => $ip,
679
+ );
680
+ }
681
+ }
682
+ break;
683
+ }
684
+
685
+ if ( $data ) {
686
+ wp_cache_set( $cache_key, $data );
687
+ }
688
+ }
689
+ }
690
+ }
691
+
692
+ return $data;
693
+ }
694
+
695
+ /**
696
+ * Checks if a temporary blocked IP is active.
697
+ *
698
+ * @param string $blocked_ip The blocked IP record from the DB.
699
+ */
700
+ public function is_blocked_ip_active( $blocked_ip ) {
701
+ $current_datetime = strtotime( current_time( 'mysql' ) );
702
+ $start_block = strtotime( $blocked_ip['start_block'] );
703
+ $end_block = strtotime( $blocked_ip['end_block'] );
704
+ if (
705
+ $current_datetime >= $start_block &&
706
+ $current_datetime < $end_block
707
+ ) {
708
+ return true;
709
+ }
710
+
711
+ return false;
712
+ }
713
+
714
+ /**
715
+ * Checks if an IP has been blacklisted.
716
+ *
717
+ * @param string $ip The IP address to check.
718
+ */
719
+ public function get_blacklisted_ip( $ip ) {
720
+ $blacklisted_ip = $this->table_query(
721
+ 'blacklist',
722
+ array(
723
+ 'type' => 'row',
724
+ 'select' => array(
725
+ 'blacklist_service',
726
+ 'blacklist_id',
727
+ 'last_updated',
728
+ 'attempts',
729
+ ),
730
+ 'where' => array(
731
+ array(
732
+ array(
733
+ 'key' => 'user_ip',
734
+ 'value' => $ip,
735
+ 'relation' => '=',
736
+ ),
737
+ ),
738
+ ),
739
+ 'limit' => 1,
740
+ )
741
+ );
742
+
743
+ return $blacklisted_ip;
744
+ }
745
+
746
+ /**
747
+ * Checks if an IP has been blocked.
748
+ *
749
+ * @param string $ip The IP address to check.
750
+ */
751
+ public function get_blocked_ip( $ip ) {
752
+ $blocked_ip = $this->table_query(
753
+ 'blocked',
754
+ array(
755
+ 'type' => 'row',
756
+ 'select' => array(
757
+ 'blocked_type',
758
+ 'start_block',
759
+ 'end_block',
760
+ 'reason',
761
+ 'attempts',
762
+ ),
763
+ 'where' => array(
764
+ array(
765
+ array(
766
+ 'key' => 'user_ip',
767
+ 'value' => $ip,
768
+ 'relation' => '=',
769
+ ),
770
+ ),
771
+ ),
772
+ 'limit' => 1,
773
+ )
774
+ );
775
+
776
+ return $blocked_ip;
777
+ }
778
+
779
+ /**
780
+ * Queries a database table.
781
+ *
782
+ * @param string $table Table key.
783
+ * @param array $args Array of query arguments.
784
+ */
785
+ public function table_query( $table, $args = array() ) {
786
+ global $wpdb;
787
+
788
+ $sql = 'SELECT';
789
+
790
+ // Select.
791
+ $select = '';
792
+ if ( ! empty( $args['select'] ) ) {
793
+ foreach ( $args['select'] as $key => $value ) {
794
+ if ( $select ) {
795
+ $select .= ', ';
796
+ }
797
+ $select .= $value;
798
+ }
799
+ } else {
800
+ $select = '*';
801
+ }
802
+
803
+ $sql .= ' ' . $select;
804
+
805
+ // From.
806
+ $sql .= ' FROM ' . $this->tables[ $table ];
807
+
808
+ // Where.
809
+ $where = '';
810
+ if ( ! empty( $args['where'] ) ) {
811
+ foreach ( $args['where'] as $key => $where_stmt ) {
812
+ if ( ! $where ) {
813
+ $where .= 'WHERE ';
814
+ }
815
+
816
+ foreach ( $where_stmt as $k => $array ) {
817
+ $where .= $array['key'];
818
+ switch ( $array['relation'] ) {
819
+ case '=':
820
+ $where .= ' = ';
821
+ if ( is_numeric( $array['value'] ) ) {
822
+ $where .= ' ' . $array['value'];
823
+ } else {
824
+ $where .= ' "' . $array['value'] . '"';
825
+ }
826
+ break;
827
+ }
828
+ }
829
+ }
830
+ }
831
+
832
+ $sql .= ' ' . $where;
833
+
834
+ // Limit.
835
+ if ( ! empty( $args['limit'] ) ) {
836
+ $sql .= ' LIMIT ' . $args['limit'];
837
+ }
838
+
839
+ // Offset.
840
+ if ( ! empty( $args['offset'] ) ) {
841
+ $sql .= ' OFFSET ' . $args['offset'];
842
+ }
843
+
844
+ if ( ! empty( $args['type'] ) ) {
845
+ if ( 'row' === $args['type'] ) {
846
+ return $wpdb->get_row( $sql, ARRAY_A );
847
+ }
848
+ } else {
849
+ return $wpdb->get_results( $sql, ARRAY_A );
850
+ }
851
+ }
852
+
853
+ /**
854
+ * Returns the whitelisted IPs.
855
+ */
856
+ public function get_whitelisted_ips() {
857
+ $whitelist = explode( PHP_EOL, $this->options['ip_whitelist'] );
858
+ if ( ! $whitelist ) {
859
+ return array();
860
+ }
861
+
862
+ $whitelisted = array();
863
+ foreach ( $whitelist as $k => $whitelisted_ip ) {
864
+ $whitelisted[ $whitelisted_ip ] = $whitelisted_ip;
865
+ }
866
+
867
+ $whitelisted = apply_filters( 'wpzerospam_whitelisted_ips', $whitelisted );
868
+
869
+ return $whitelisted;
870
+ }
871
+
872
+ /**
873
+ * Gets a cookie.
874
+ *
875
+ * @param string $cookie_key The cookie of the cookie to retrieve.
876
+ */
877
+ public function get_cookie( $cookie_key ) {
878
+ $cookie_key = 'wpzerospam_' . $cookie_key;
879
+
880
+ return ! empty( $_COOKIE[ $cookie_key ] ) ? $_COOKIE[ $cookie_key ] : false;
881
+ }
882
+
883
+ /**
884
+ * Sets a cookie.
885
+ *
886
+ * @param string $cookie_key The cookie key.
887
+ * @param string $value The value of the cookie.
888
+ */
889
+ public function set_cookie( $cookie_key, $value, $expiration ) {
890
+ $current_time = current_time( 'mysql' );
891
+
892
+ setcookie( 'wpzerospam_' . $cookie_key, $value, $expiration, COOKIEPATH, COOKIE_DOMAIN );
893
+ }
894
+
895
+ /**
896
+ * Returns the current user's IP address.
897
+ *
898
+ * @link https://www.benmarshall.me/get-ip-address/
899
+ */
900
+ public function get_user_ip() {
901
+ foreach (
902
+ array(
903
+ 'HTTP_CLIENT_IP',
904
+ 'HTTP_X_FORWARDED_FOR',
905
+ 'HTTP_X_FORWARDED',
906
+ 'HTTP_X_CLUSTER_CLIENT_IP',
907
+ 'HTTP_FORWARDED_FOR',
908
+ 'HTTP_FORWARDED',
909
+ 'REMOTE_ADDR',
910
+ )
911
+ as $key ) {
912
+ if ( array_key_exists( $key, $_SERVER ) === true ) {
913
+ foreach ( explode(',', $_SERVER[ $key ]) as $ip_address ) {
914
+ $ip_address = trim( $ip_address );
915
+
916
+ if (
917
+ filter_var(
918
+ $ip_address,
919
+ FILTER_VALIDATE_IP,
920
+ FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
921
+ ) !== false
922
+ ) {
923
+ return $ip_address;
924
+ }
925
+ }
926
+ }
927
+ }
928
+
929
+ return false;
930
+ }
931
+ }
inc/helpers.php CHANGED
@@ -7,285 +7,6 @@
7
  * @link https://benmarshall.me/wordpress-zero-spam/
8
  */
9
 
10
- /**
11
- * Sets a cookie
12
- */
13
- if ( ! function_exists( 'wpzerospam_set_cookie' ) ) {
14
- function wpzerospam_set_cookie( $key, $value ) {
15
- $options = wpzerospam_options();
16
- $expiration = current_time( 'timestamp' ) + ( $options['cookie_expiration'] * DAY_IN_SECONDS );
17
-
18
- setcookie( 'wpzerospam_' . $key, $value, $expiration, COOKIEPATH, COOKIE_DOMAIN );
19
- }
20
- }
21
-
22
- /**
23
- * Get a cookie
24
- */
25
- if ( ! function_exists( 'wpzerospam_get_cookie' ) ) {
26
- function wpzerospam_get_cookie( $key ) {
27
- if ( ! empty( $_COOKIE[ 'wpzerospam_' . $key ] ) ) {
28
- return $_COOKIE[ 'wpzerospam_' . $key ];
29
- }
30
-
31
- return false;
32
- }
33
- }
34
-
35
- /**
36
- * Check access
37
- *
38
- * Determines if the current user IP should have access to the site.
39
- *
40
- * @return array Includes info about the access check.
41
- */
42
- if ( ! function_exists( 'wpzerospam_check_access' ) ) {
43
- function wpzerospam_check_access() {
44
- $ip = wpzerospam_ip();
45
- //$ip = '46.229.168.150'; // StopForumSpam testing IP
46
- //$ip = '120.40.130.70'; // BotScout testing IP
47
-
48
- // Innocent until proven guilty...
49
- $access = [ 'access' => true, 'ip' => $ip ];
50
-
51
- // Always allow authenticated users & users trying to log in access
52
- if ( is_user_logged_in() || wpzerospam_is_login() ) {
53
- return $access;
54
- }
55
-
56
- /**
57
- * Only check access for actual page vists. Some resource requests like
58
- * favicons fire this function which causes duplicate entries in the DB.
59
- *
60
- * @TODO - Find a way to avoid these checks & ensure this function only gets
61
- * fired on page requests vs. resources.
62
- */
63
- if (
64
- ! is_singular() && ! is_page() && ! is_single() && ! is_archive() &&
65
- ! is_home() && ! is_front_page()
66
- ) {
67
- return $access;
68
- }
69
-
70
- $options = wpzerospam_options();
71
-
72
- // 1. Check whitelisted IP addresses
73
- $whitelist = wpzerospam_get_whitelist();
74
- if ( $whitelist && array_key_exists( $ip, $whitelist ) ) {
75
- return $access;
76
- }
77
-
78
- // 2. Check if the user's IP address has been blocked
79
- $blocked = wpzerospam_is_blocked( $ip );
80
- if ( $blocked ) {
81
- $access['access'] = false;
82
- $access['reason'] = $blocked['reason'];
83
-
84
- return $access;
85
- }
86
-
87
- // 3. Check the blacklist
88
- $blacklisted = wpzerospam_is_blacklisted( $ip );
89
- if ( $blacklisted ) {
90
- /**
91
- * IP found in the blacklist, check to see if the record needs to be
92
- * updated.
93
- */
94
- $api_blacklisted = wpzerospam_is_api_blacklisted( $ip, $blacklisted );
95
- if ( $api_blacklisted ) {
96
- // IP address blacklisted record found & updated
97
- $access['access'] = false;
98
- $access['reason'] = $api_blacklisted['blacklist_service'];
99
-
100
- return $access;
101
- }
102
- }
103
-
104
- // 4. Check the API blacklists
105
- $api_blacklisted = wpzerospam_is_api_blacklisted( $ip );
106
- if ( $api_blacklisted ) {
107
- $access['access'] = false;
108
- $access['reason'] = $api_blacklisted['blacklist_service'];
109
-
110
- return $access;
111
- }
112
-
113
- return $access;
114
- }
115
- }
116
-
117
- /**
118
- * Checks & updates blacklist records from APIs
119
- *
120
- * @param string $ip IP address to check.
121
- * @param array $blacklisted_record IP blacklist record from the DB
122
- * @return boolean/array False is not blacklisted, otherwise an array with the
123
- * blacklisted IP information.
124
- */
125
- if ( ! function_exists( 'wpzerospam_is_api_blacklisted' ) ) {
126
- function wpzerospam_is_api_blacklisted( $ip, $blacklisted_record = false ) {
127
- global $wpdb;
128
-
129
- // No need to check everytime a user visits a page
130
- if ( wpzerospam_get_cookie( 'api_blacklist' ) ) { return false; }
131
- wpzerospam_set_cookie( 'api_blacklist', current_time( 'timestamp' ) );
132
-
133
- $options = wpzerospam_options();
134
-
135
- if ( $blacklisted_record ) {
136
- // Check/update existing blacklist record
137
-
138
- $last_updated = strtotime( $blacklisted_record['last_updated'] );
139
- $current_time = current_time( 'timestamp' );
140
- $expiration = $last_updated + MONTH_IN_SECONDS;
141
-
142
- if ( $current_time > $expiration ) {
143
- // Expired, update the blacklist record in the DB
144
- $query = wpzerospam_query_blacklist_api(
145
- $ip,
146
- $blacklisted_record['blacklist_service']
147
- );
148
-
149
- if ( $query ) {
150
- if (
151
- // Check for Stop Forum Spam confidence level
152
- ( ! empty( $query['confidence'] ) && $query['confidence'] < $options['stopforumspam_confidence_min'] ) ||
153
- // Check for BotScout counts
154
- ( ! empty( $query['count'] ) && $query['count'] < $options['botscout_count_min'] )
155
- ) {
156
- // Does not meet the stopforumspam confidence minimum, delete record
157
- $wpdb->delete( wpzerospam_tables( 'blacklist' ), [
158
- 'blacklist_id' => $blacklisted_record['blacklist_id']
159
- ]);
160
-
161
- return false;
162
- } else {
163
- // Blacklist API found a matching record, update the DB one
164
- $blacklisted_record['last_updated'] = current_time( 'mysql' );
165
- $blacklisted_record['blacklist_data'] = json_encode( $query );
166
-
167
- $wpdb->update( wpzerospam_tables( 'blacklist' ), $blacklisted_record, [
168
- 'blacklist_id' => $blacklisted_record['blacklist_id']
169
- ]);
170
-
171
- return $blacklisted_record;
172
- }
173
- } else {
174
- // Blacklist API didn't find a matching record, delete the DB one
175
- $wpdb->delete( wpzerospam_tables( 'blacklist' ), [
176
- 'blacklist_id' => $blacklisted_record['blacklist_id']
177
- ]);
178
-
179
- return false;
180
- }
181
- } else {
182
- // Not expired
183
- return $blacklisted_record;
184
- }
185
- } else {
186
- // Check all available blacklist APIs
187
- $stopforumspam = wpzerospam_query_blacklist_api( $ip, 'stopforumspam' );
188
- if ( $stopforumspam ) {
189
- if ( ! empty( $stopforumspam['confidence'] ) && $stopforumspam['confidence'] < $options['stopforumspam_confidence_min'] ) {
190
- // Does not meet the stopforumspam confidence minimum, delete record
191
- $wpdb->delete( wpzerospam_tables( 'blacklist' ), [
192
- 'user_ip' => $ip
193
- ]);
194
-
195
- return false;
196
- }
197
-
198
- $blacklisted_record = [
199
- 'blacklist_service' => 'stopforumspam',
200
- 'user_ip' => $ip,
201
- 'last_updated' => current_time( 'mysql' ),
202
- 'blacklist_data' => json_encode( $stopforumspam )
203
- ];
204
-
205
- $wpdb->replace( wpzerospam_tables( 'blacklist' ), $blacklisted_record );
206
-
207
- return $blacklisted_record;
208
- }
209
-
210
- $botscout = wpzerospam_query_blacklist_api( $ip, 'botscout' );
211
- if ( $botscout ) {
212
- if ( ! empty( $botscout['count'] ) && $botscout['count'] < $options['botscout_count_min'] ) {
213
- // Does not meet the botscout count minimum, delete record
214
- $wpdb->delete( wpzerospam_tables( 'blacklist' ), [
215
- 'user_ip' => $ip
216
- ]);
217
-
218
- return false;
219
- }
220
-
221
- $blacklisted_record = [
222
- 'blacklist_service' => 'botscout',
223
- 'user_ip' => $ip,
224
- 'last_updated' => current_time( 'mysql' ),
225
- 'blacklist_data' => json_encode( $botscout )
226
- ];
227
-
228
- $wpdb->replace( wpzerospam_tables( 'blacklist' ), $blacklisted_record );
229
-
230
- return $blacklisted_record;
231
- }
232
- }
233
-
234
- return false;
235
- }
236
- }
237
-
238
- /**
239
- * Adds a access attempt from a blocked user
240
- *
241
- * @param string $reason The reason for the block
242
- */
243
- if ( ! function_exists( 'wpzerospam_attempt_blocked' ) ) {
244
- function wpzerospam_attempt_blocked( $ip, $reason ) {
245
- global $wpdb;
246
-
247
- $options = wpzerospam_options();
248
-
249
- // Check blocked tables
250
- $blocked = wpzerospam_is_blocked( $ip );
251
- if ( $blocked ) {
252
- // IP already exists in the blocked IP table, increment attempt
253
- $attempts = $blocked['attempts'];
254
- $attempts++;
255
-
256
- $wpdb->update( wpzerospam_tables( 'blocked' ), [
257
- 'attempts' => $attempts
258
- ], [
259
- 'blocked_id' => $blocked['blocked_id']
260
- ]);
261
- }
262
-
263
- // Check $blacklisted table
264
- $blacklisted = wpzerospam_is_blacklisted( $ip );
265
- if ( $blacklisted ) {
266
- // IP already exists in the blacklisted IP table, increment attempt
267
- $attempts = $blacklisted['attempts'];
268
- $attempts++;
269
-
270
- $wpdb->update( wpzerospam_tables( 'blacklist' ), [
271
- 'attempts' => $attempts
272
- ], [
273
- 'blacklist_id' => $blacklisted['blacklist_id']
274
- ]);
275
- }
276
-
277
- wpzerospam_log_detection( 'blocked', [ 'reason' => $reason ] );
278
-
279
- if ( 'redirect' == $options['block_handler'] ) {
280
- wp_redirect( esc_url( $options['blocked_redirect_url'] ) );
281
- exit();
282
- } else {
283
- status_header( 403 );
284
- die( $options['blocked_message'] );
285
- }
286
- }
287
- }
288
-
289
  /**
290
  * Logs a spam detection.
291
  *
7
  * @link https://benmarshall.me/wordpress-zero-spam/
8
  */
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  /**
11
  * Logs a spam detection.
12
  *
inc/utilities.php CHANGED
@@ -152,70 +152,6 @@ if ( ! function_exists( 'wpzerospam_options' ) ) {
152
  }
153
  }
154
 
155
- /**
156
- * Queries a blacklist API
157
- *
158
- * @param string $ip The IP address to query
159
- * @param string $service The API service to query
160
- * @return false/array False if not found, otherwise the IP info.
161
- */
162
- if ( ! function_exists( 'wpzerospam_query_blacklist_api' ) ) {
163
- function wpzerospam_query_blacklist_api( $ip, $service ) {
164
- $options = wpzerospam_options();
165
-
166
- switch( $service ) {
167
- case 'stopforumspam':
168
- if ( 'enabled' != $options['stop_forum_spam'] ) { return false; }
169
-
170
- $api_url = 'https://api.stopforumspam.org/api?';
171
- $params = [ 'ip' => $ip, 'json' => '' ];
172
- $endpoint = $api_url . http_build_query( $params );
173
- break;
174
- case 'botscout':
175
- if ( empty( $options['botscout_api'] ) ) { return false; }
176
-
177
- $api_url = 'https://botscout.com/test/?';
178
- $params = [ 'ip' => $ip, 'key' => $options['botscout_api'] ];
179
- $endpoint = $api_url . http_build_query( $params );
180
- break;
181
- }
182
-
183
- if ( ! empty( $endpoint ) ) {
184
- $response = wp_remote_get( $endpoint, [ 'timeout' => $options['api_timeout'] ] );
185
- if ( is_array( $response ) && ! is_wp_error( $response ) ) {
186
- $data = wp_remote_retrieve_body( $response );
187
-
188
- switch( $service ) {
189
- case 'stopforumspam':
190
- $data = json_decode( $data, true );
191
- if (
192
- ! empty( $data['success'] ) &&
193
- $data['success'] &&
194
- ! empty( $data['ip'] ) &&
195
- ! empty( $data['ip']['appears'] )
196
- ) {
197
- return $data['ip'];
198
- }
199
- break;
200
- case 'botscout':
201
- if ( strpos( $data, '!' ) === false ) {
202
- list( $matched, $type, $count ) = explode( "|", $data );
203
- if ( 'Y' == $matched ) {
204
- return [
205
- 'type' => $type,
206
- 'count' => $count
207
- ];
208
- }
209
- }
210
- break;
211
- }
212
- }
213
- }
214
-
215
- return false;
216
- }
217
- }
218
-
219
  /**
220
  * Query the database tables
221
  *
@@ -329,30 +265,6 @@ if ( ! function_exists( 'wpzerospam_types' ) ) {
329
  }
330
  }
331
 
332
- /**
333
- * Whitelisted IPs
334
- *
335
- * @return array An array of whitelisted IP addresses.
336
- */
337
- if ( ! function_exists( 'wpzerospam_get_whitelist' ) ) {
338
- function wpzerospam_get_whitelist() {
339
- $options = wpzerospam_options();
340
- if ( $options['ip_whitelist'] ) {
341
- $whitelist = explode( PHP_EOL, $options['ip_whitelist'] );
342
- if ( $whitelist ) {
343
- $whitelisted = [];
344
- foreach( $whitelist as $k => $whitelisted_ip ) {
345
- $whitelisted[ $whitelisted_ip ] = $whitelisted_ip;
346
- }
347
-
348
- return $whitelisted;
349
- }
350
- }
351
-
352
- return false;
353
- }
354
- }
355
-
356
  /**
357
  * Checks if an IP is blocked
358
  *
@@ -395,24 +307,3 @@ if ( ! function_exists( 'wpzerospam_is_blocked' ) ) {
395
  return false;
396
  }
397
  }
398
-
399
- /**
400
- * Checks if an IP is blacklisted
401
- *
402
- * @param string IP address to check.
403
- * @return boolean/array False is not blacklisted, otherwise an array with the
404
- * blacklisted IP information.
405
- */
406
- if ( ! function_exists( 'wpzerospam_is_blacklisted' ) ) {
407
- function wpzerospam_is_blacklisted( $ip ) {
408
- $blacklist_ip = wpzerospam_query_table( 'blacklist', [
409
- 'select' => [
410
- 'blacklist_service', 'blacklist_id', 'last_updated', 'attempts'
411
- ],
412
- 'where' => [ 'user_ip' => $ip ],
413
- 'limit' => 1
414
- ]);
415
-
416
- return $blacklist_ip;
417
- }
418
- }
152
  }
153
  }
154
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  /**
156
  * Query the database tables
157
  *
265
  }
266
  }
267
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
  /**
269
  * Checks if an IP is blocked
270
  *
307
  return false;
308
  }
309
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
readme.txt CHANGED
@@ -1,11 +1,11 @@
1
  === WordPress Zero Spam ===
2
  Contributors: bmarshall511, jaredatch, EusebiuOprinoiu
3
  Tags: comments, spam, antispam, anti-spam, comment spam, spambot, spammer, spam free, spam blocker, registration spam
4
- Donate link: https://benmarshall.me/donate/?utm_source=wordpress_zero_spam&utm_medium=wordpress_repo&utm_campaign=donate
5
  Requires at least: 5.2
6
  Tested up to: 5.5
7
  Requires PHP: 7.1
8
- Stable tag: 4.9.13
9
  License: GNU GPLv3
10
  License URI: https://choosealicense.com/licenses/gpl-3.0/
11
 
@@ -15,7 +15,7 @@ WordPress Zero Spam makes blocking spam & malicious visitors a cinch. Just insta
15
 
16
  Quit forcing users to answer silly questions, read confusing captchas, or take additional steps just to prove they're not spam. Stop malicious bots & hackers in their tracks before they ever have a chance to infiltrate your site &mdash; **introducing WordPress Zero Spam**.
17
 
18
- [WordPress Zero Spam](https://benmarshall.me/wordpress-zero-spam/?utm_source=wordpress.org&utm_medium=plugin&utm_campaign=wordpress_zero_spam) uses AI in combination with proven spam detection techniques and a database of known malicious IPs from around the world to detect and block unwanted visitors.
19
 
20
  In addition, it integrates with other popular plugins to provide all around protection. **Just install, activate, and enjoy a spam-free site!**
21
 
@@ -47,42 +47,39 @@ In addition, it integrates with other popular plugins to provide all around prot
47
  * [Formidable Form Builder](https://wordpress.org/plugins/formidable/) submissions
48
  * and can be easily integrated into any existing theme or plugin
49
 
50
- WordPress Zero Spam is great at blocking spam &mdash; as a site owner there's more you can do to [stop WordPress spam](https://benmarshall.me/stop-wordpress-spam/) in its tracks.
51
 
52
  = Multilingual Supported =
53
 
54
- We’ve integrated multi language support within the framework of our plugin, so you get a translated dashboard out of the box, and developer options to add even more languages.
 
 
 
55
 
56
  = Developer API =
57
 
58
  WordPress Zero Spam is free and open source. It’s the perfect solution to stopping spam and can be extended and integrated further. It was created and developed with the developer in mind, and we have already seen some truly remarkable addons already developed.
59
 
60
- To help you get started and learn just how to integrate with WordPress Zero Spam, visit the [plugin's documentation](https://benmarshall.me/wordpress-zero-spam/?utm_source=wordpress.org&utm_medium=plugin&utm_campaign=wordpress_zero_spam).
61
 
62
- = Translations =
63
 
64
- * [French](https://translate.wordpress.org/locale/fr/default/wp-plugins/zero-spam/) (fr_FR)
65
- * [Italian](https://translate.wordpress.org/locale/it/default/wp-plugins/zero-spam/) – (it_IT)
66
 
67
- = Be a contributor =
68
 
69
- If you want to contribute, go to the [WordPress Zero Spam GitHub Repository](https://github.com/bmarshall511/wordpress-zero-spam) and see where you can help. You can also add a new language via [translate.wordpress.org](https://translate.wordpress.org/projects/wp-plugins/zero-spam/).
70
 
71
- = Documentation and Support =
72
-
73
- * For documentation and tutorials, view the [documentation](https://benmarshall.me/wordpress-zero-spam/?utm_source=wordpress.org&utm_medium=plugin&utm_campaign=wordpress_zero_spam).
74
- * If you have any more questions, visit our support on the [Plugin’s Forum](https://wordpress.org/support/plugin/zero-spam/).
75
- * For more information, FAQs and API documentation, check out [Zero Spam](https://zerospam.org/?utm_source=wordpress.org&utm_medium=plugin&utm_campaign=wordpress_zero_spam).
76
 
77
  = WordPress Zero Spam needs your support =
78
 
79
- **WordPress Zero Spam is free — completely free & always will be.** It is hard to continue development and support for this free plugin without contributions from users like you. If you enjoy using WordPress Zero Spam and find it useful, please consider making a [donation](https://benmarshall.me/donate/?utm_source=wordpress.org&utm_medium=plugin&utm_campaign=wordpress_zero_spam). Your donation will help encourage and support the plugin’s continued development and better user support.
80
-
81
- You can also show your support by:
82
 
83
- * liking our [Facebook Page](https://www.facebook.com/zerospamorg/);
84
- * following us on [Twitter](https://www.facebook.com/zerospamorg);
85
- * or rating us on [WordPress](https://wordpress.org/support/plugin/zero-spam/reviews/?filter=5/#new-post) 🙂.
86
 
87
  == Installation ==
88
 
@@ -90,7 +87,7 @@ You can also show your support by:
90
  2. Activate the plugin through the Plugins screen (*Plugins > Installed Plugins*).
91
  3. Visit the plugin setting to configure as needed (*Settings > WP Zero Spam*).
92
 
93
- For more information & developer documentation, see the [plugin’s website](https://benmarshall.me/wordpress-zero-spam).
94
 
95
  == Frequently Asked Questions ==
96
 
@@ -126,11 +123,11 @@ Example with `wpzerospam` class:
126
 
127
  `<form name="registerform" class="wpzerospam" action="https://yourdomain.local/login/?action=register" method="post" novalidate="novalidate">`
128
 
129
- If you need help, please don't hesitate to [reach out](https://benmarshall.me/contact/?utm_source=wordpress.org&utm_medium=plugin&utm_campaign=wordpress_zero_spam).
130
 
131
  = How do I integrate this into another plugin or theme? =
132
 
133
- It's easy as adding the class `wpzerospam` to the `form` element, then adding a check in the form processor that the `wpzerospam_key` post value matches the option value in the database using the `wpzerospam_key_check()` helper function. See the [plugin's documentation](https://benmarshall.me/wordpress-zero-spam/?utm_source=wordpress.org&utm_medium=plugin&utm_campaign=wordpress_zero_spam) for more information on available hooks & functions.
134
 
135
  = Is JavaScript required to check form submissions? =
136
 
@@ -150,6 +147,14 @@ Yes. One of the many techniques WordPress Zero Spam employs requires JavaScript
150
 
151
  == Changelog ==
152
 
 
 
 
 
 
 
 
 
153
  = 4.9.13 =
154
 
155
  * Fix - PHP notices for comment options [#209](https://github.com/bmarshall511/wordpress-zero-spam/issues/209)
1
  === WordPress Zero Spam ===
2
  Contributors: bmarshall511, jaredatch, EusebiuOprinoiu
3
  Tags: comments, spam, antispam, anti-spam, comment spam, spambot, spammer, spam free, spam blocker, registration spam
4
+ Donate link: https://www.benmarshall.me/donate/?utm_source=wordpress_zero_spam&utm_medium=wordpress_repo&utm_campaign=donate
5
  Requires at least: 5.2
6
  Tested up to: 5.5
7
  Requires PHP: 7.1
8
+ Stable tag: 4.10.0
9
  License: GNU GPLv3
10
  License URI: https://choosealicense.com/licenses/gpl-3.0/
11
 
15
 
16
  Quit forcing users to answer silly questions, read confusing captchas, or take additional steps just to prove they're not spam. Stop malicious bots & hackers in their tracks before they ever have a chance to infiltrate your site &mdash; **introducing WordPress Zero Spam**.
17
 
18
+ [WordPress Zero Spam](https://www.benmarshall.me/wordpress-zero-spam/?utm_source=wordpress.org&utm_medium=plugin&utm_campaign=wordpress_zero_spam) uses AI in combination with proven spam detection techniques and a database of known malicious IPs from around the world to detect and block unwanted visitors.
19
 
20
  In addition, it integrates with other popular plugins to provide all around protection. **Just install, activate, and enjoy a spam-free site!**
21
 
47
  * [Formidable Form Builder](https://wordpress.org/plugins/formidable/) submissions
48
  * and can be easily integrated into any existing theme or plugin
49
 
50
+ WordPress Zero Spam is great at blocking spam &mdash; as a site owner there's more you can do to [stop WordPress spam](https://www.benmarshall.me/stop-wordpress-spam/) in its tracks.
51
 
52
  = Multilingual Supported =
53
 
54
+ We’ve integrated multi language support within the framework of our plugin, so you get a translated dashboard out of the box, and developer options to add even more languages. Contribute new languages via [translate.wordpress.org](https://translate.wordpress.org/projects/wp-plugins/zero-spam/).
55
+
56
+ * [French](https://translate.wordpress.org/locale/fr/default/wp-plugins/zero-spam/) – (fr_FR)
57
+ * [Italian](https://translate.wordpress.org/locale/it/default/wp-plugins/zero-spam/) – (it_IT)
58
 
59
  = Developer API =
60
 
61
  WordPress Zero Spam is free and open source. It’s the perfect solution to stopping spam and can be extended and integrated further. It was created and developed with the developer in mind, and we have already seen some truly remarkable addons already developed.
62
 
63
+ To help you get started and learn just how to integrate with WordPress Zero Spam, visit the [plugin's documentation](https://www.benmarshall.me/wordpress-zero-spam/?utm_source=wordpress.org&utm_medium=plugin&utm_campaign=wordpress_zero_spam).
64
 
 
65
 
66
+ = Help test & become a contributor =
 
67
 
68
+ Help test future releases and contribute on the [WordPress Zero Spam GitHub Repository](https://github.com/bmarshall511/wordpress-zero-spam).
69
 
70
+ = Plugin Support =
71
 
72
+ * For usage &amp; tutorials, view the [documentation](https://www.benmarshall.me/wordpress-zero-spam/?utm_source=wordpress.org&utm_medium=plugin&utm_campaign=wordpress_zero_spam).
73
+ * Have questions? Visit the [WordPress Zero Spam Forum](https://wordpress.org/support/plugin/zero-spam/).
74
+ * More FAQs and API documentation can be found at [Zero Spam](https://zerospam.org/?utm_source=wordpress.org&utm_medium=plugin&utm_campaign=wordpress_zero_spam).
 
 
75
 
76
  = WordPress Zero Spam needs your support =
77
 
78
+ **WordPress Zero Spam is free — completely free & always will be.** It is hard to continue development and support for this free plugin without contributions from users like you. If you enjoy using WordPress Zero Spam and find it useful, please consider making a [donation](https://www.benmarshall.me/donate/?utm_source=wordpress.org&utm_medium=plugin&utm_campaign=wordpress_zero_spam). Your donation will help encourage and support the plugin’s continued development and better user support.
 
 
79
 
80
+ * Like our [Facebook Page](https://www.facebook.com/zerospamorg/)
81
+ * Follow us on [Twitter](https://www.facebook.com/zerospamorg)
82
+ * Rate us on [WordPress](https://wordpress.org/support/plugin/zero-spam/reviews/?filter=5/#new-post)
83
 
84
  == Installation ==
85
 
87
  2. Activate the plugin through the Plugins screen (*Plugins > Installed Plugins*).
88
  3. Visit the plugin setting to configure as needed (*Settings > WP Zero Spam*).
89
 
90
+ For more information & developer documentation, see the [plugin’s website](https://www.benmarshall.me/wordpress-zero-spam).
91
 
92
  == Frequently Asked Questions ==
93
 
123
 
124
  `<form name="registerform" class="wpzerospam" action="https://yourdomain.local/login/?action=register" method="post" novalidate="novalidate">`
125
 
126
+ If you need help, please don't hesitate to [reach out](https://www.benmarshall.me/contact/?utm_source=wordpress.org&utm_medium=plugin&utm_campaign=wordpress_zero_spam).
127
 
128
  = How do I integrate this into another plugin or theme? =
129
 
130
+ It's easy as adding the class `wpzerospam` to the `form` element, then adding a check in the form processor that the `wpzerospam_key` post value matches the option value in the database using the `wpzerospam_key_check()` helper function. See the [plugin's documentation](https://www.benmarshall.me/wordpress-zero-spam/?utm_source=wordpress.org&utm_medium=plugin&utm_campaign=wordpress_zero_spam) for more information on available hooks & functions.
131
 
132
  = Is JavaScript required to check form submissions? =
133
 
147
 
148
  == Changelog ==
149
 
150
+ = 4.10.0 =
151
+
152
+ * Enhancement - Various performance improvements & caching added.
153
+ * Enhancement - Easier to use class methods for integrating Zero Spam into any plugin or theme.
154
+ * Enhancement - New `WordPress_Zero_Spam` class added.
155
+ * Enhancement - IPs are now checked against known, safe hosts and user agents (i.e. search engine crawlers).
156
+ * Enhancement - Added advanced debugging functionality.
157
+
158
  = 4.9.13 =
159
 
160
  * Fix - PHP notices for comment options [#209](https://github.com/bmarshall511/wordpress-zero-spam/issues/209)
wordpress-zero-spam.php CHANGED
@@ -13,7 +13,7 @@
13
  * Plugin Name: WordPress Zero Spam
14
  * Plugin URI: https://benmarshall.me/wordpress-zero-spam
15
  * Description: Tired of all the useless and bloated WordPress spam plugins? The WordPress Zero Spam plugin makes blocking spam a cinch. <strong>Just install, activate and say goodbye to spam.</strong> Based on work by <a href="http://davidwalsh.name/wordpress-comment-spam" target="_blank">David Walsh</a>.
16
- * Version: 4.9.13
17
  * Requires at least: 5.2
18
  * Requires PHP: 7.2
19
  * Author: Ben Marshall
@@ -24,15 +24,30 @@
24
  * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
25
  */
26
 
27
- /**
28
- * Security Note: Blocks direct access to the plugin PHP files.
29
- */
30
- defined( 'ABSPATH' ) or die( 'No script kiddies please!' );
31
 
32
- // Define plugin constants
33
  define( 'WORDPRESS_ZERO_SPAM', __FILE__ );
34
  define( 'WORDPRESS_ZERO_SPAM_DB_VERSION', '0.5' );
35
- define( 'WORDPRESS_ZERO_SPAM_VERSION', '4.9.13' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
  /**
38
  * Utility helper functions.
@@ -122,25 +137,3 @@ if ( wpzerospam_plugin_integration_enabled( 'fluentform' ) ) {
122
  if ( wpzerospam_plugin_integration_enabled( 'formidable' ) ) {
123
  require plugin_dir_path( WORDPRESS_ZERO_SPAM ) . 'integrations/formidable/formidable.php';
124
  }
125
-
126
- /**
127
- * Plugin redirect functionality
128
- */
129
- if ( ! function_exists( 'wpzerospam_template_redirect' ) ) {
130
- function wpzerospam_template_redirect() {
131
- // No need to check everytime a user visits a page
132
- if ( wpzerospam_get_cookie( 'last_check' ) ) { return false; }
133
-
134
- $options = wpzerospam_options();
135
-
136
- // Check if the current user has access to the site
137
- $access = wpzerospam_check_access();
138
-
139
- if ( ! $access['access'] ) {
140
- wpzerospam_attempt_blocked( $access['ip'], $access['reason'] );
141
- } else {
142
- wpzerospam_set_cookie( 'last_check', current_time( 'timestamp' ) );
143
- }
144
- }
145
- }
146
- add_action( 'template_redirect', 'wpzerospam_template_redirect' );
13
  * Plugin Name: WordPress Zero Spam
14
  * Plugin URI: https://benmarshall.me/wordpress-zero-spam
15
  * Description: Tired of all the useless and bloated WordPress spam plugins? The WordPress Zero Spam plugin makes blocking spam a cinch. <strong>Just install, activate and say goodbye to spam.</strong> Based on work by <a href="http://davidwalsh.name/wordpress-comment-spam" target="_blank">David Walsh</a>.
16
+ * Version: 4.10.0
17
  * Requires at least: 5.2
18
  * Requires PHP: 7.2
19
  * Author: Ben Marshall
24
  * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
25
  */
26
 
27
+ // Security Note: Blocks direct access to the plugin PHP files.
28
+ defined( 'ABSPATH' ) || die();
 
 
29
 
30
+ // Define plugin constants.
31
  define( 'WORDPRESS_ZERO_SPAM', __FILE__ );
32
  define( 'WORDPRESS_ZERO_SPAM_DB_VERSION', '0.5' );
33
+ define( 'WORDPRESS_ZERO_SPAM_VERSION', '4.10.0' );
34
+
35
+ /**
36
+ * Include the WordPress Zero Spam plugin class.
37
+ */
38
+ require plugin_dir_path( WORDPRESS_ZERO_SPAM ) . 'classes/class-wordpress-zero-spam.php';
39
+
40
+ // Initialize the plugin.
41
+ $wordpress_zero_spam = new WordPress_Zero_Spam();
42
+
43
+
44
+
45
+
46
+
47
+
48
+
49
+
50
+
51
 
52
  /**
53
  * Utility helper functions.
137
  if ( wpzerospam_plugin_integration_enabled( 'formidable' ) ) {
138
  require plugin_dir_path( WORDPRESS_ZERO_SPAM ) . 'integrations/formidable/formidable.php';
139
  }