PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.8.1
Defender Security – Malware Scanner, Login Security & Firewall v5.8.1
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / src / component / ip / class-antibot-global-firewall.php

class-antibot-global-firewall.php in Defender Security – Malware Scanner, Login Security & Firewall 5.8.1, at src/component/ip/class-antibot-global-firewall.php

808 lines 22.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Responsible for handling AntiBot Global Firewall.
4 *
5 * @package WP_Defender\Component\IP
6 */
7
8 namespace WP_Defender\Component\IP;
9
10 use Exception;
11 use Generator;
12 use WP_Defender\Behavior\WPMUDEV;
13 use WP_Defender\Component;
14 use WP_Defender\Controller\Firewall;
15 use WP_Defender\Integrations\Antibot_Global_Firewall_Client;
16 use WP_Defender\Model\Antibot_Global_Firewall as Antibot_Global_Firewall_Model;
17 use WP_Defender\Model\Setting\Antibot_Global_Firewall_Setting;
18 use WP_Defender\Traits\Defender_Dashboard_Client;
19 use WP_Error;
20
21 /**
22 * Provides methods for downloading IPs from AntiBot Global Firewall, storing, deleting and checking IPs in DB table.
23 */
24 class Antibot_Global_Firewall extends Component {
25 use Defender_Dashboard_Client;
26
27 public const REASON_SLUG = 'antibot_global_firewall';
28
29 public const LOG_FILE_NAME = 'antibot-global-firewall.log';
30
31 public const DOWNLOAD_SYNC_SCHEDULE = 'twicedaily';
32
33 public const DOWNLOAD_SYNC_NEXT_RUN_OPTION = 'wpdef_antibot_global_firewall_download_sync_next_run';
34
35 public const LAST_SYNC_OPTION = 'wpdef_antibot_global_firewall_last_sync';
36
37 public const NOTICE_SLUG = 'wpdef_show_antibot_global_firewall_notice';
38
39 public const BLOCKLIST_STATS_KEY = 'wpdef_antibot_global_firewall_stats';
40
41 public const IS_SWITCHING_TO_PLUGIN_IN_PROGRESS = 'wpdef_antibot_global_firewall_switching_to_plugin_in_progress';
42
43 public const GLOBAL_NOTICE_TIME_OPTION = 'wpdef_antibot_global_firewall_global_notice_time';
44
45 public const GLOBAL_NOTICE_DELAY_DAYS_OPTION = 60;
46
47 /**
48 * The AntiBot Global Firewall model for storing IPs.
49 *
50 * @var Antibot_Global_Firewall_Model
51 */
52 private $model;
53
54 /**
55 * The AntiBot Global Firewall setting model.
56 *
57 * @var Antibot_Global_Firewall_Setting
58 */
59 private $model_setting;
60
61 /**
62 * The WPMUDEV object.
63 *
64 * @var WPMUDEV
65 */
66 private $wpmudev;
67
68 /**
69 * The client object for AntiBot Global Firewall.
70 *
71 * @var Antibot_Global_Firewall_Client
72 */
73 private $antibot_client;
74
75 /**
76 * Lock file name for firewall updates.
77 *
78 * @var string
79 */
80 protected string $lock_filename = 'antibot_global_firewall.lock';
81
82 /**
83 * Initializes the class with the Antibot_Global_Firewall_Model, Antibot_Global_Firewall_Setting, WPMUDEV and Antibot_Global_Firewall_Client instances.
84 *
85 * @param Antibot_Global_Firewall_Model $model The AntiBot Global Firewall model.
86 * @param Antibot_Global_Firewall_Setting $model_setting The AntiBot Global FIrewall setting model.
87 * @param WPMUDEV $wpmudev The WPMUDEV object.
88 * @param Antibot_Global_Firewall_Client $antibot_client The client object for AntiBot Global Firewall API.
89 */
90 public function __construct( Antibot_Global_Firewall_Model $model, Antibot_Global_Firewall_Setting $model_setting, WPMUDEV $wpmudev, Antibot_Global_Firewall_Client $antibot_client ) {
91 $this->model = $model;
92 $this->model_setting = $model_setting;
93 $this->wpmudev = $wpmudev;
94 $this->antibot_client = $antibot_client;
95
96 add_action( 'wpdef_confirm_antibot_toggle_on_hosting', array( $this, 'confirm_toggle_on_hosting' ) );
97 add_action( 'wp_loaded', array( $this, 'clear_antibot_on_disconnection' ) );
98 }
99
100 /**
101 * Check if AntiBot Global Firewall feature is enabled.
102 *
103 * @return bool True for enabled or false for disabled.
104 */
105 public function is_enabled(): bool {
106 $is_enabled = $this->model_setting->enabled;
107
108 /**
109 * Filter to enable or disable the AntiBot Global Firewall.
110 *
111 * @param bool $is_enabled True for enabled or false for disabled.
112 */
113 return (bool) apply_filters( 'wpdef_antibot_enabled', $is_enabled );
114 }
115
116 /**
117 * Check if the AntiBot Global Firewall is enabled on WPMU DEV.
118 *
119 * @return bool True if the AntiBot Global Firewall is enabled, false otherwise.
120 */
121 public function hosting_is_enabled(): bool {
122 return true === defender_get_hosting_feature_state( 'antibot' );
123 }
124
125 /**
126 * Check if the AntiBot Global Firewall is enabled based on managed by.
127 *
128 * @return bool True if the AntiBot Global Firewall is enabled, false otherwise.
129 */
130 public function frontend_is_enabled(): bool {
131 return 'plugin' === $this->get_managed_by() ?
132 ! $this->is_expired_membership_type() && $this->is_enabled() :
133 $this->hosting_is_enabled();
134 }
135
136 /**
137 * Check if the AntiBot Global Firewall is enabled and the site is connected to the HUB.
138 *
139 * @return bool True if the AntiBot Global Firewall is active, false otherwise.
140 */
141 public function is_active(): bool {
142 return $this->frontend_is_enabled() && $this->is_site_connected_to_hub_via_hcm_or_dash();
143 }
144
145 /**
146 * Check if the AntiBot Global Firewall is active via plugin.
147 *
148 * @since 5.1.1
149 * @return bool True if the AntiBot Global Firewall is active via plugin, false otherwise.
150 */
151 public function is_active_via_plugin(): bool {
152 return 'plugin' === $this->get_managed_by() && $this->is_enabled() && $this->is_site_connected_to_hub_via_hcm_or_dash();
153 }
154
155 /**
156 * Check if the AntiBot Global Firewall is active via hosting.
157 *
158 * @since 5.6.0
159 * @return bool True if the AntiBot Global Firewall is active via hosting, false otherwise.
160 */
161 public function is_active_via_hosting(): bool {
162 return 'hosting' === $this->get_managed_by() && $this->hosting_is_enabled();
163 }
164
165 /**
166 * Check if the IP is blocked.
167 *
168 * @param string $ip The IP address.
169 *
170 * @return bool True if the IP is blocked, false otherwise.
171 */
172 public function is_ip_blocked( string $ip ): bool {
173 $result = $this->model->get_by_ip( $ip );
174
175 return isset( $result->unlocked ) && 1 !== $result->unlocked;
176 }
177
178 /**
179 * Delete complete blocklist.
180 */
181 public function delete_blocklist() {
182 $this->model->truncate();
183
184 delete_site_option( self::LAST_SYNC_OPTION );
185 }
186
187 /**
188 * Check if the Unlock Me Captcha should be displayed.
189 *
190 * @param array $ips The IP addresses.
191 *
192 * @return bool True if the IP is blocked by AntiBot Global Firewall, false otherwise.
193 */
194 public function is_displayed( array $ips ): bool {
195 if ( ! $this->is_enabled() ) {
196 return false;
197 }
198
199 foreach ( $ips as $ip ) {
200 if ( $this->is_ip_blocked( $ip ) ) {
201 return true;
202 }
203 }
204
205 return false;
206 }
207
208 /**
209 * Get the Unlock Me Captcha button text.
210 *
211 * @return string The button text.
212 */
213 public static function get_button_text(): string {
214 return esc_html__( 'AntiBot Unlock Me', 'defender-security' );
215 }
216
217 /**
218 * Handle the download and store blocklist.
219 *
220 * @since 4.8.0
221 * @return void
222 */
223 public function download_and_store_blocklist(): void {
224 if ( ! $this->is_site_connected_to_hub_via_hcm_or_dash() ) {
225 return;
226 }
227
228 if ( $this->has_lock( $this->lock_filename ) ) {
229 $this->log( 'Fallback as already a process is running', Firewall::FIREWALL_LOG );
230 return;
231 }
232
233 $this->create_lock( $this->lock_filename );
234 $file_path = $this->download_blocklist();
235
236 if ( is_string( $file_path ) && '' !== $file_path ) {
237 $this->store_blocklist( $file_path );
238 }
239 $this->remove_lock( $this->lock_filename );
240 }
241
242 /**
243 * Download blocklist.
244 *
245 * @since 4.8.0
246 * @return string|void
247 */
248 private function download_blocklist() {
249 $response = $this->antibot_client->get_blocklist_download( $this->model_setting->mode );
250
251 if ( is_wp_error( $response ) ) {
252 $this->log( sprintf( 'AntiBot Global Firewall Error: %s', $response->get_error_message() ), Firewall::FIREWALL_LOG );
253 return;
254 } elseif ( isset( $response['status'] ) && 'error' === $response['status'] ) {
255 $this->log( sprintf( 'AntiBot Global Firewall Error: %s', $response['message'] ), Firewall::FIREWALL_LOG );
256 return;
257 } elseif (
258 ! isset( $response['data']['download_url'] )
259 || ! is_string( $response['data']['download_url'] )
260 || '' === $response['data']['download_url']
261 || ! isset( $response['data']['hashes']['sha256'] )
262 || ! is_string( $response['data']['hashes']['sha256'] )
263 || '' === $response['data']['hashes']['sha256']
264 ) {
265 $this->log( 'AntiBot Global Firewall Error: Download link not found in the response.', Firewall::FIREWALL_LOG );
266 return;
267 }
268
269 $file_url = $response['data']['download_url'];
270
271 if ( ! function_exists( 'download_url' ) ) {
272 require_once ABSPATH . 'wp-admin/includes/file.php';
273 }
274
275 $tmp_file = download_url( $file_url );
276
277 if ( is_wp_error( $tmp_file ) ) {
278 $this->log( 'AntiBot Global Firewall Error: Unable to download file', Firewall::FIREWALL_LOG );
279 return;
280 }
281
282 $expected_file_hash = '';
283 $response = wp_remote_get( $response['data']['hashes']['sha256'] );
284 if ( wp_remote_retrieve_response_code( $response ) === 200 ) {
285 $expected_file_hash = trim( wp_remote_retrieve_body( $response ) );
286 }
287
288 $file_hash = hash_file( 'sha256', $tmp_file );
289 if ( $file_hash !== $expected_file_hash ) {
290 $this->log( 'AntiBot Global Firewall Error: File is not legit. Hash does not match.', Firewall::FIREWALL_LOG );
291 return;
292 }
293
294 $this->log( 'AntiBot Global Firewall: Downloaded ' . $this->model_setting->get_mode_label() . ' blocklist successfully.', Firewall::FIREWALL_LOG );
295
296 return $tmp_file;
297 }
298
299 /**
300 * Store blocklist.
301 *
302 * @param string $file_path File path.
303 *
304 * @since 4.8.0
305 * @return void
306 */
307 private function store_blocklist( string $file_path ): void {
308 try {
309 $generator = $this->read_file_generator( $file_path );
310
311 $this->delete_blocklist();
312 $this->model->bulk_insert( $generator );
313
314 self::set_last_sync();
315
316 $this->log( 'AntiBot Global Firewall: IPs stored in the DB table.', Firewall::FIREWALL_LOG );
317 } catch ( Exception $e ) {
318 $this->log( 'AntiBot Global Firewall Error: ' . $e->getMessage(), Firewall::FIREWALL_LOG );
319 }
320
321 // Remove temporary file.
322 wp_delete_file( $file_path );
323 }
324
325 /**
326 * Read file generator.
327 *
328 * @param string $file_path File path.
329 *
330 * @return Generator
331 * @throws Exception If file not found or could not open the file.
332 */
333 private function read_file_generator( $file_path ) {
334 if ( ! file_exists( $file_path ) ) {
335 throw new Exception( 'File not found.' );
336 }
337
338 global $wp_filesystem;
339 $lines = $wp_filesystem->get_contents_array( $file_path );
340
341 if ( ! is_array( $lines ) || array() === $lines ) {
342 throw new Exception( 'Could not retrieve the file contents!' );
343 }
344
345 try {
346 foreach ( $lines as $line ) {
347 yield trim( $line );
348 }
349 } catch ( Exception $exception ) {
350 throw $exception;
351 }
352 }
353
354 /**
355 * Get the blocklisted IP count.
356 *
357 * @return string The blocklisted IP count.
358 */
359 public function get_blocklisted_ip_count(): string {
360 // Check if the feature is enabled.
361 if ( ! $this->frontend_is_enabled() ) {
362 return '0';
363 }
364 // Since from v5.0.2 one method is used for counting.
365 return number_format( $this->get_cached_blocklisted_ips() );
366 }
367
368 /**
369 * Get the last sync timestamp.
370 *
371 * @return false|int The last sync timestamp.
372 */
373 public static function get_last_sync() {
374 return get_site_option( self::LAST_SYNC_OPTION, false );
375 }
376
377 /**
378 * Set the last sync timestamp.
379 */
380 public static function set_last_sync(): void {
381 update_site_option( self::LAST_SYNC_OPTION, time() );
382 }
383
384 /**
385 * Check if the IPs should be downloaded from Antibot service.
386 *
387 * @return bool True if the IPs should be downloaded, false otherwise.
388 */
389 public function maybe_download(): bool {
390 if ( $this->wpmudev->is_wpmu_hosting() && 'plugin' !== $this->get_managed_by() ) {
391 return false;
392 }
393
394 $last_sync = self::get_last_sync();
395
396 if ( false === $last_sync ) {
397 return true;
398 }
399
400 return time() - $last_sync >= 12 * HOUR_IN_SECONDS;
401 }
402
403 /**
404 * Get the default managed by.
405 *
406 * @return string The default managed by.
407 */
408 public function get_default_managed_by(): string {
409 return $this->wpmudev->is_wpmu_hosting() ? 'hosting' : 'plugin';
410 }
411
412 /**
413 * Get the AntiBot managed by.
414 *
415 * @return string The AntiBot managed by.
416 */
417 public function get_managed_by(): string {
418 $managed_by = $this->model_setting->managed_by;
419
420 return '' !== $managed_by ? $managed_by : $this->get_default_managed_by();
421 }
422
423 /**
424 * Get the AntiBot managed by label.
425 *
426 * @return string The AntiBot managed by label.
427 */
428 public function get_managed_by_label(): string {
429 return 'plugin' === $this->get_managed_by() ?
430 esc_html__( 'Defender Plugin', 'defender-security' ) :
431 esc_html__( 'WPMU DEV Hosting', 'defender-security' );
432 }
433
434 /**
435 * Set the AntiBot managed by.
436 *
437 * @param string $managed_by The managed by value.
438 *
439 * @return bool True if the managed by is set, false otherwise.
440 */
441 public function set_managed_by( string $managed_by ): bool {
442 if ( in_array( $managed_by, Antibot_Global_Firewall_Setting::MANAGED_BY_ALLOWED, true ) ) {
443 $this->model_setting->managed_by = $managed_by;
444 $this->model_setting->save();
445
446 return true;
447 }
448
449 return false;
450 }
451
452 /**
453 * Switch the AntiBot managed by.
454 *
455 * @return string|false The managed by value if it's switched, false otherwise.
456 */
457 public function switch_managed_by() {
458 $managed_by = 'plugin' === $this->get_managed_by() ? 'hosting' : 'plugin';
459
460 if ( $this->set_managed_by( $managed_by ) ) {
461 if ( 'plugin' === $managed_by ) {
462 return false === $this->managed_by_plugin_action() ? false : $managed_by;
463 } else {
464 return false === $this->managed_by_hosting_action() ? false : $managed_by;
465 }
466 }
467
468 return false;
469 }
470
471 /**
472 * Action when the AntiBot Global Firewall is enabled/disabled by plugin.
473 *
474 * @param bool $enabled True to enable, false to disable.
475 *
476 * @return bool True if the enabled/disabled action is successful, false otherwise.
477 */
478 public function managed_by_plugin_action( bool $enabled = true ): bool {
479 if ( $enabled ) {
480 set_site_transient( self::IS_SWITCHING_TO_PLUGIN_IN_PROGRESS, true, 30 );
481
482 // Disable AntiBot on hosting if it's currently enabled.
483 $this->toggle_on_hosting( false, 0.1 );
484
485 // Schedule a single event to confirm AntiBot is disabled on hosting after 15 seconds.
486 if ( ! wp_next_scheduled( 'wpdef_confirm_antibot_toggle_on_hosting' ) ) {
487 wp_schedule_single_event( time() + 15, 'wpdef_confirm_antibot_toggle_on_hosting', array( false ) );
488 }
489
490 // Enable AntiBot on plugin side.
491 $this->model_setting->enabled = true;
492 $this->model_setting->save();
493
494 // Download IPs.
495 $this->download_and_store_blocklist();
496 } else {
497 // Disable AntiBot on plugin side.
498 $this->model_setting->enabled = false;
499 $this->model_setting->save();
500
501 // Remove IPs.
502 $this->delete_blocklist();
503 }
504
505 return true;
506 }
507
508 /**
509 * Action when the AntiBot Global Firewall is enabled/disabled by hosting.
510 *
511 * @param bool $enabled True to enable, false to disable.
512 * @return bool True if the enabled/disabled action is successful, false otherwise.
513 */
514 public function managed_by_hosting_action( bool $enabled = true ): bool {
515 if ( $enabled ) {
516 wp_clear_scheduled_hook( 'wpdef_confirm_antibot_toggle_on_hosting' );
517 delete_site_transient( self::IS_SWITCHING_TO_PLUGIN_IN_PROGRESS );
518
519 // Disable AntiBot on plugin side.
520 $this->model_setting->enabled = false;
521 $this->model_setting->save();
522
523 // Remove IPs from DB table.
524 $this->delete_blocklist();
525
526 // Enable AntiBot on hosting if it's currently disabled.
527 $result = $this->toggle_on_hosting( true );
528 if ( is_wp_error( $result ) ) {
529 return false;
530 }
531 } else {
532 $result = $this->toggle_on_hosting( false );
533 if ( is_wp_error( $result ) ) {
534 return false;
535 }
536 }
537
538 return true;
539 }
540
541 /**
542 * Toggle the AntiBot Global Firewall on hosting.
543 *
544 * @param bool $enable True to enable, false to disable.
545 * @param float $timeout The timeout for the request.
546 *
547 * @return bool|WP_Error True if the AntiBot Global Firewall is toggled, false if not, WP_Error otherwise.
548 */
549 public function toggle_on_hosting( bool $enable, float $timeout = 30 ) {
550 if ( ! $this->wpmudev->is_wpmu_hosting() ) {
551 return false;
552 }
553
554 $hosting_enable = defender_get_hosting_feature_state( 'antibot' );
555
556 if (
557 ( $enable && ! $hosting_enable ) ||
558 ( ! $enable && $hosting_enable )
559 ) {
560 $this->attach_behavior( WPMUDEV::class, WPMUDEV::class );
561
562 $body = array( 'is_active' => $enable );
563 if ( $enable ) {
564 $body['mode'] = $this->get_hosting_mode();
565 }
566
567 $data = $this->make_wpmu_request(
568 WPMUDEV::API_ANTIBOT_GLOBAL_FIREWALL,
569 $body,
570 array(
571 'method' => 'PUT',
572 'timeout' => $timeout,
573 )
574 );
575
576 if ( $timeout > 1 && is_wp_error( $data ) ) {
577 $this->log( 'AntiBot Global Firewall Error: ' . $data->get_error_message(), Firewall::FIREWALL_LOG );
578 return $data;
579 }
580 }
581
582 return true;
583 }
584
585 /**
586 * Fetches the number of blocklisted IPs from cache or from the Blocklist API.
587 *
588 * If the value is not cached, it will fetch the number of blocklisted IPs from the Blocklist API and cache it.
589 *
590 * @return int The number of blocklisted IPs.
591 */
592 public function get_cached_blocklisted_ips(): int {
593 $mode = $this->frontend_mode();
594 $stats_key = self::BLOCKLIST_STATS_KEY . '_' . $mode;
595 $cached_data = get_site_transient( $stats_key );
596 if ( false !== $cached_data ) {
597 return (int) $cached_data;
598 }
599
600 $blocklist_stats = $this->antibot_client->get_blocklist_stats();
601
602 if ( is_wp_error( $blocklist_stats ) ) {
603 $this->log( 'AntiBot Global Firewall Error: ' . $blocklist_stats->get_error_message(), Firewall::FIREWALL_LOG );
604 return 0;
605 }
606
607 $blocklisted_ips_key = Antibot_Global_Firewall_Setting::MODE_BASIC === $mode ? 'blocked_ips' : 'strict_blocked_ips';
608 if ( ! isset( $blocklist_stats[ $blocklisted_ips_key ] ) || ! is_int( $blocklist_stats[ $blocklisted_ips_key ] ) || 0 >= $blocklist_stats[ $blocklisted_ips_key ] ) {
609 $this->log( 'AntiBot Global Firewall Error: Stats missing for mode: ' . $mode, Firewall::FIREWALL_LOG );
610 return 0;
611 }
612
613 $blocklisted_ips = $blocklist_stats[ $blocklisted_ips_key ];
614 set_site_transient( $stats_key, $blocklisted_ips, 12 * HOUR_IN_SECONDS );
615
616 return $blocklisted_ips;
617 }
618
619 /**
620 * Confirm the toggle on hosting.
621 *
622 * @param bool $enable True to enable, false to disable.
623 *
624 * @return void
625 */
626 public function confirm_toggle_on_hosting( bool $enable ): void {
627 $this->toggle_on_hosting( $enable );
628 delete_site_transient( self::IS_SWITCHING_TO_PLUGIN_IN_PROGRESS );
629 }
630
631 /**
632 * Logs IP-related messages if logging is enabled via filter.
633 *
634 * @param string $message The message to be logged.
635 */
636 public function log_ip_message( string $message ): void {
637 /**
638 * Filters whether IP logging is enabled.
639 *
640 * This filter allows developers to enable or disable IP logging globally.
641 * Returning false will prevent the log message from being written.
642 *
643 * @param bool $is_enabled Whether IP logging is enabled. Default true.
644 * @since 5.1.0
645 */
646 $is_logging_enabled = (bool) apply_filters( 'wpdef_antibot_global_firewall_ip_log', true );
647
648 if ( ! $is_logging_enabled ) {
649 return;
650 }
651
652 $this->log( $message, self::LOG_FILE_NAME );
653 }
654
655 /**
656 * Get the AntiBot mode status in DB.
657 *
658 * @return string The AntiBot mode.
659 */
660 public function get_mode(): string {
661 return $this->model_setting->mode;
662 }
663
664 /**
665 * Get the AntiBot mode on WPMU DEV.
666 *
667 * @return string The AntiBot mode label.
668 */
669 public function get_hosting_mode(): string {
670 $mode = defender_get_hosting_feature_state( 'antibot_mode' );
671
672 return '' !== $mode ? $mode : Antibot_Global_Firewall_Setting::MODE_BASIC;
673 }
674
675 /**
676 * Get AntiBot mode based on managed by.
677 *
678 * @return string The AntiBot mode.
679 */
680 public function frontend_mode(): string {
681 return 'plugin' === $this->get_managed_by() ?
682 $this->get_mode() :
683 $this->get_hosting_mode();
684 }
685
686 /**
687 * Switch the AntiBot mode.
688 *
689 * @return string|false|WP_Error The AntiBot mode value if it's switched, false otherwise.
690 */
691 public function switch_mode() {
692 if ( 'plugin' === $this->get_managed_by() ) {
693 $mode = $this->get_mode();
694
695 $this->model_setting->mode = Antibot_Global_Firewall_Setting::MODE_STRICT === $mode
696 ? Antibot_Global_Firewall_Setting::MODE_BASIC
697 : Antibot_Global_Firewall_Setting::MODE_STRICT;
698 $this->model_setting->save();
699
700 $this->download_and_store_blocklist();
701
702 delete_site_transient( self::BLOCKLIST_STATS_KEY . '_' . $mode );
703 } else {
704 if ( ! $this->wpmudev->is_wpmu_hosting() ) {
705 return false;
706 }
707
708 $mode = $this->get_hosting_mode();
709
710 $this->attach_behavior( WPMUDEV::class, WPMUDEV::class );
711 $data = $this->make_wpmu_request(
712 WPMUDEV::API_ANTIBOT_GLOBAL_FIREWALL,
713 array(
714 'is_active' => $this->hosting_is_enabled(),
715 'mode' => Antibot_Global_Firewall_Setting::MODE_STRICT === $mode
716 ? Antibot_Global_Firewall_Setting::MODE_BASIC
717 : Antibot_Global_Firewall_Setting::MODE_STRICT,
718 ),
719 array( 'method' => 'PUT' )
720 );
721
722 if ( is_wp_error( $data ) ) {
723 $this->log( 'AntiBot Global Firewall Error: ' . $data->get_error_message(), Firewall::FIREWALL_LOG );
724 return $data;
725 }
726
727 delete_site_transient( self::BLOCKLIST_STATS_KEY . '_' . $mode );
728 }
729
730 return $this->frontend_mode();
731 }
732
733 /**
734 * Clear antibot table when site is disconnected from HUB.
735 *
736 * @return void
737 */
738 public function clear_antibot_on_disconnection(): void {
739 if ( $this->is_site_connected_to_hub_via_hcm_or_dash() ) {
740 return;
741 }
742 if ( $this->get_cached_blocklisted_ips() <= 0 ) {
743 return;
744 }
745 $this->delete_blocklist();
746 delete_site_transient( self::BLOCKLIST_STATS_KEY . '_' . $this->get_mode() );
747 delete_site_transient( self::BLOCKLIST_STATS_KEY . '_' . $this->get_hosting_mode() );
748 $this->log( 'Antibot table cleared due to site disconnection.', self::LOG_FILE_NAME );
749 }
750
751 /**
752 * Get the timestamp for when the notice should start showing.
753 *
754 * @return false|int The timestamp or false if not set.
755 */
756 public function get_global_notice_time() {
757 return (int) get_site_option( self::GLOBAL_NOTICE_TIME_OPTION, 0 );
758 }
759
760 /**
761 * Set a timestamp for when the notice should start showing.
762 *
763 * @return void
764 */
765 public function maybe_set_notice_time(): void {
766 if ( 0 === $this->get_global_notice_time() ) {
767 $future = time() + ( DAY_IN_SECONDS * self::GLOBAL_NOTICE_DELAY_DAYS_OPTION );
768 update_site_option( self::GLOBAL_NOTICE_TIME_OPTION, $future );
769 }
770 }
771
772 /**
773 * Disable the global notice.
774 *
775 * @return void
776 */
777 public function dismiss_global_notice(): void {
778 update_site_option( self::GLOBAL_NOTICE_TIME_OPTION, -1 );
779 }
780
781 /**
782 * Determine if the global notice should be shown.
783 *
784 * @return bool True if the notice should be shown, false otherwise.
785 */
786 public function should_show_global_notice(): bool {
787 // Is FREE or PRO plugin?
788 if ( $this->wpmudev->is_pro() ) {
789 return false;
790 }
791
792 // Is AntiBot enabled?
793 if ( $this->frontend_is_enabled() ) {
794 return false;
795 }
796
797 // Has 60 days passed since installation?
798 $notice_time = $this->get_global_notice_time();
799
800 // No notice time or marked as dismissed (-1) or not yet reached the time.
801 if ( false === $notice_time || -1 === $notice_time || time() < $notice_time ) {
802 return false;
803 }
804
805 return true;
806 }
807 }
808