PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.2.1
Defender Security – Malware Scanner, Login Security & Firewall v6.2.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 / controller / class-antibot-global-firewall.php

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

739 lines 21.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * It handles functionalities such as saving settings and handle the request for downloading the IPs from AntiBot Global Firewall.
4 *
5 * @package WP_Defender\Controller
6 */
7
8 namespace WP_Defender\Controller;
9
10 use Calotes\Component\Request;
11 use Calotes\Component\Response;
12 use WP_Defender\Component\Network_Cron_Manager;
13 use WP_Defender\Component\Config\Config_Hub_Helper;
14 use WP_Defender\Event;
15 use WP_Defender\Traits\Setting;
16 use WP_Defender\Traits\Defender_Dashboard_Client;
17 use WP_Defender\Model\Setting\Antibot_Global_Firewall_Setting;
18 use WP_Defender\Behavior\WPMUDEV;
19 use WP_Defender\Component\IP\Antibot_Global_Firewall as Antibot_Global_Firewall_Component;
20 use WP_Defender\Component\Blacklist_Lockout;
21 use WP_Defender\Component\IP\Global_IP;
22 use WP_Defender\Component\Scheduler\Scheduler;
23 use WP_Defender\Helper\Analytics\Antibot as Antibot_Analytics;
24
25 /**
26 * Class Antibot_Global_Firewall.
27 */
28 class Antibot_Global_Firewall extends Event {
29 use Setting;
30 use Defender_Dashboard_Client;
31
32 /**
33 * The slug identifier for this controller.
34 *
35 * @var string
36 */
37 protected $slug = 'wdf-ip-lockout';
38
39 /**
40 * The model for handling AntiBot Global Firewall settings.
41 *
42 * @var Antibot_Global_Firewall_Setting
43 */
44 protected $model;
45
46 /**
47 * The service for handling AntiBot Global Firewall logic.
48 *
49 * @var Antibot_Global_Firewall_Component
50 */
51 protected $service;
52
53 /**
54 * The WPMUDEV instance.
55 *
56 * @var WPMUDEV
57 */
58 private $wpmudev;
59
60 /**
61 * Initializes the class with model and service.
62 *
63 * @param Antibot_Global_Firewall_Setting $model The model for handling AntiBot Global Firewall settings.
64 * @param Antibot_Global_Firewall_Component $service The service for handling AntiBot Global Firewall logic.
65 * @param WPMUDEV $wpmudev The WPMUDEV instance.
66 */
67 public function __construct( Antibot_Global_Firewall_Setting $model, Antibot_Global_Firewall_Component $service, WPMUDEV $wpmudev ) {
68 $this->register_routes();
69 add_action( 'defender_enqueue_assets', array( $this, 'enqueue_assets' ) );
70
71 $this->model = $model;
72 $this->service = $service;
73 $this->wpmudev = $wpmudev;
74
75 add_action( 'admin_init', array( $this, 'maybe_hcm_connection_attempt' ), 20 );
76 add_action( 'wpdef_hub_connector_synced', array( $this, 'maybe_hcm_connection_attempt' ) );
77 add_action( 'wpmudev_hub_connector_first_sync_completed', array( $this, 'maybe_hcm_connection_attempt' ) );
78
79 /**
80 * Download and store Blocklist from the API.
81 */
82 if ( $this->service->is_active_via_plugin() ) {
83 /**
84 * Network Cron Manager
85 *
86 * @var Network_Cron_Manager $network_cron_manager
87 */
88 $network_cron_manager = wd_di()->get( Network_Cron_Manager::class );
89 $network_cron_manager->register_callback(
90 'wpdef_antibot_global_firewall_fetch_blocklist',
91 array( $this, 'handle_download_and_store_blocklist' ),
92 12 * HOUR_IN_SECONDS,
93 time() + 15
94 );
95 } elseif ( wp_next_scheduled( 'wpdef_antibot_global_firewall_fetch_blocklist' ) ) {
96 wp_clear_scheduled_hook( 'wpdef_antibot_global_firewall_fetch_blocklist' );
97 }
98
99 if ( $this->wpmudev->is_wpmu_hosting() ) {
100 add_action( 'init', array( $this, 'sync_state' ) );
101 }
102 add_action( 'init', array( $this, 'handle_expired_membership' ) );
103 }
104
105 /**
106 * Handle request to save settings.
107 *
108 * @param Request $request The request object.
109 *
110 * @defender_route
111 * @return Response
112 */
113 public function save_settings( Request $request ) {
114 $data = $request->get_data(
115 array(
116 'enabled' => array( 'type' => 'bool' ),
117 'managed_by' => array( 'type' => 'string' ),
118 // Temporary property.
119 'module_title' => array(
120 'type' => 'string',
121 'sanitize' => 'sanitize_text_field',
122 ),
123 'module_location' => array(
124 'type' => 'string',
125 'sanitize' => 'sanitize_text_field',
126 ),
127 'redirect_to_feature_page' => array( 'type' => 'bool' ),
128 // End.
129 )
130 );
131 $old_enabled = $this->model->enabled;
132
133 $location = 'Feature Page';
134 // Split module's titles and locations.
135 if ( isset( $data['module_title'] ) && 'antibot' === $data['module_title'] ) {
136 if ( ! isset( $data['module_location'] ) || '' === $data['module_location'] ) {
137 $location = 'Dashboard';
138 } else {
139 $location = $data['module_location'];
140 unset( $data['module_location'] );
141 }
142 unset( $data['module_title'] );
143 }
144
145 $this->model->import( $data );
146 if ( $this->model->validate() ) {
147 $this->model->save();
148
149 if ( 'plugin' === $this->service->get_managed_by() ) {
150 if ( $old_enabled !== $this->model->enabled ) {
151 if ( ! $this->model->enabled ) {
152 $this->service->delete_blocklist();
153 } elseif ( $this->model->enabled && $this->service->maybe_download() ) {
154 $this->service->download_and_store_blocklist();
155 }
156 }
157 } else {
158 $old_enabled = $this->service->frontend_is_enabled();
159 $result = $this->service->toggle_on_hosting( $this->model->enabled );
160
161 if ( is_wp_error( $result ) ) {
162 return new Response(
163 false,
164 array( 'message' => esc_html__( 'Something wrong happened, please try again!', 'defender-security' ) )
165 );
166 }
167 }
168
169 Config_Hub_Helper::set_clear_active_flag();
170 // Maybe track.
171 if ( ! defender_is_wp_cli() && $old_enabled !== $data['enabled'] ) {
172 wd_di()->get( Antibot_Analytics::class )->track_antibot( $old_enabled, $location );
173 }
174
175 $update_message = $this->get_update_message( $data, $old_enabled, Antibot_Global_Firewall_Setting::get_module_name() );
176 $referrer = wp_get_referer();
177 if ( $referrer && strpos( $referrer, 'page=wp-defender' ) !== false ) {
178 $update_message = sprintf(
179 /* translators: 1: Module name. */
180 __( '%s is now enabled.', 'defender-security' ),
181 '<strong>' . Antibot_Global_Firewall_Setting::get_module_name() . '</strong>'
182 );
183 }
184
185 $data_frontend = $this->data_frontend();
186
187 if ( isset( $data['redirect_to_feature_page'] ) && $data['redirect_to_feature_page'] ) {
188 $data_frontend['redirect'] = network_admin_url( 'admin.php?page=wdf-ip-lockout' );
189 }
190
191 return new Response(
192 true,
193 array_merge(
194 array(
195 'message' => $update_message,
196 'auto_close' => true,
197 ),
198 $data_frontend
199 )
200 );
201 }
202
203 return new Response(
204 false,
205 array( 'message' => $this->model->get_formatted_errors() )
206 );
207 }
208
209 /**
210 * Queue assets and require data.
211 *
212 * @return void
213 */
214 public function enqueue_assets() {
215 if ( ! $this->is_page_active() ) {
216 return;
217 }
218 }
219
220 /**
221 * All the variables that we will show on frontend, both in the main page, or dashboard widget.
222 *
223 * @return array
224 */
225 public function data_frontend(): array {
226 $model_export = $this->model->export();
227 $model_export['managed_by'] = $this->service->get_managed_by();
228 $module_name = Antibot_Global_Firewall_Setting::get_module_name();
229
230 return array_merge(
231 array(
232 'model' => $model_export,
233 'misc' => array(
234 'module_slug' => Antibot_Global_Firewall_Setting::get_module_slug(),
235 'module_name' => $module_name,
236 'show_notice' => false,
237 'sync_schedule' => __( 'Twice Daily', 'defender-security' ),
238 'ips_count' => $this->service->get_blocklisted_ip_count(),
239 'blocklist_stats_full' => $this->service->get_cached_blocklist_stats_data(),
240 'frontend_is_enabled' => $this->service->frontend_is_enabled(),
241 'frontend_mode' => $this->service->frontend_mode(),
242 'is_active' => $this->service->is_active(),
243 'show_stats_button' => ! $this->wpmudev->is_whitelabel_enabled(),
244 'show_checker' => ! $this->wpmudev->is_wpmu_hosting()
245 || $this->wpmudev->is_wpmu_dev_admin()
246 || ! ( $this->service->is_active_via_hosting() && $this->wpmudev->is_whitelabel_enabled() ),
247 'active_tooltip_text' => __( 'List of exploit attempts detected and blocked across all connected sites by AntiBot Firewall.', 'defender-security' ),
248 'inactive_tooltip_text' => sprintf(
249 /* translators: %s: Module name. */
250 __( '%s is Inactive.', 'defender-security' ),
251 $module_name
252 ),
253 'current_user' => esc_html( wp_get_current_user()->display_name ?? __( 'User', 'defender-security' ) ),
254 'is_expired_membership' => $this->is_expired_membership_type(),
255 'show_antibot_options' => $this->service->get_states_of_antibot_options_for_different_hosting_types(),
256 ),
257 ),
258 $this->dump_routes_and_nonces()
259 );
260 }
261
262 /**
263 * Download and store blocklist.
264 *
265 * @return void
266 */
267 public function handle_download_and_store_blocklist(): void {
268 if ( is_multisite() ) {
269 $next_run = (int) get_site_option( Antibot_Global_Firewall_Component::DOWNLOAD_SYNC_NEXT_RUN_OPTION, 0 );
270 if ( $next_run > 0 && $next_run > time() ) {
271 return;
272 }
273
274 $interval = (int) wd_di()->get( Scheduler::class )->get_cron_schedule_interval( Antibot_Global_Firewall_Component::DOWNLOAD_SYNC_SCHEDULE );
275 $next_run = time() + ( $interval > 0 ? $interval : 12 * HOUR_IN_SECONDS );
276 update_site_option( Antibot_Global_Firewall_Component::DOWNLOAD_SYNC_NEXT_RUN_OPTION, $next_run );
277 }
278
279 if ( true === $this->service->is_active_via_plugin() ) {
280 $is_switch_to_main_site = is_multisite() && ! is_main_site();
281 if ( $is_switch_to_main_site ) {
282 switch_to_blog( get_main_site_id() );
283 }
284
285 $this->service->download_and_store_blocklist();
286
287 if ( $is_switch_to_main_site ) {
288 restore_current_blog();
289 }
290 }
291 }
292
293 /**
294 * Handle the HCM connection attempt via Antibot page.
295 *
296 * @return void
297 */
298 public function maybe_hcm_connection_attempt() {
299 $data = get_site_transient( Hub_Connector::TRANSIENT_KEY );
300 $module_slug = $data['module_slug'] ?? '';
301
302 if ( Antibot_Global_Firewall_Setting::get_module_slug() === $module_slug && self::get_hcm_status() ) {
303 delete_site_transient( Hub_Connector::TRANSIENT_KEY );
304
305 if ( 'plugin' === $this->service->get_managed_by() ) {
306 $this->service->managed_by_plugin_action();
307 } else {
308 $this->service->managed_by_hosting_action();
309 }
310 }
311 }
312
313 /**
314 * Export the data of this module, we will use this for export to HUB, create a preset etc.
315 *
316 * @return array
317 */
318 public function to_array(): array {
319 return array();
320 }
321
322 /**
323 * Import the data from the HUB, or from the preset.
324 *
325 * @param array $data The data to be imported.
326 *
327 * @return void
328 */
329 public function import_data( $data ) {
330 $model = $this->model;
331 if ( isset( $data['antibot'] ) ) {
332 $model->enabled = (bool) $data['antibot'];
333 } else {
334 $model->enabled = true;
335 }
336 $model->save();
337 }
338
339 /**
340 * Remove all settings, configs generated in this container runtime.
341 */
342 public function remove_settings() {}
343
344 /**
345 * Remove all data.
346 */
347 public function remove_data() {
348 $this->service->delete_blocklist();
349
350 delete_site_option( Antibot_Global_Firewall_Component::DOWNLOAD_SYNC_NEXT_RUN_OPTION );
351 // Don't display Antibot global notice. We can delete slugs about this notice in the future.
352 delete_site_option( Antibot_Global_Firewall_Component::NOTICE_SLUG );
353 delete_site_option( Antibot_Global_Firewall_Component::GLOBAL_NOTICE_TIME_OPTION );
354 // Blocklist stats data.
355 delete_site_transient( Antibot_Global_Firewall_Component::BLOCKLIST_STATS_KEY . '_' . Antibot_Global_Firewall_Setting::MODE_BASIC );
356 delete_site_transient( Antibot_Global_Firewall_Component::BLOCKLIST_STATS_KEY . '_' . Antibot_Global_Firewall_Setting::MODE_STRICT );
357 delete_site_transient( Antibot_Global_Firewall_Component::BLOCKLIST_STATS_FULL_KEY );
358 delete_site_transient( Antibot_Global_Firewall_Component::IS_SWITCHING_TO_PLUGIN_IN_PROGRESS );
359 }
360
361 /**
362 * Export strings.
363 *
364 * @return array
365 */
366 public function export_strings() {
367 return array();
368 }
369
370 /**
371 * Handle request to switch managed by.
372 *
373 * @defender_route
374 * @return Response
375 */
376 public function switch_managed_by(): Response {
377 $result = $this->service->switch_managed_by();
378 if ( false !== $result ) {
379 return new Response(
380 true,
381 array(
382 'message' => sprintf(
383 /* translators: 1. Open tag. 2. Close tag. 3. Managed by label. */
384 __( '%1$sAntiBot%2$s blocklist is now being managed by %3$s.', 'defender-security' ),
385 '<strong>',
386 '</strong>',
387 $this->service->get_managed_by_label()
388 ),
389 'managed_by' => $result,
390 'frontend_mode' => $this->service->frontend_mode(),
391 'ips_count' => $this->service->get_blocklisted_ip_count(),
392 'auto_close' => true,
393 )
394 );
395 }
396
397 return new Response(
398 false,
399 array(
400 'message' => __( 'Failed to switch. Please try again.', 'defender-security' ),
401 'auto_close' => true,
402 )
403 );
404 }
405
406 /**
407 * Various actions during form submission.
408 *
409 * @param Request $request The HTTP request object.
410 *
411 * @defender_route
412 * @return Response
413 */
414 public function blocklist_checker_submit_trusted_ip_form( Request $request ): Response {
415 $data = $request->get_data(
416 array(
417 'ip' => array(
418 'type' => 'string',
419 'sanitize' => 'sanitize_text_field',
420 ),
421 'email' => array(
422 'type' => 'string',
423 'sanitize' => 'sanitize_email',
424 ),
425 'service' => array(
426 'type' => 'string',
427 'sanitize' => 'sanitize_text_field',
428 ),
429 'reason' => array(
430 'type' => 'string',
431 'sanitize' => 'sanitize_text_field',
432 ),
433 )
434 );
435
436 $reporter_email = isset( $data['email'] ) ? trim( $data['email'] ) : '';
437 if ( '' === $reporter_email || ! is_email( $reporter_email ) ) {
438 return new Response(
439 false,
440 array(
441 'message' => esc_html__( 'A valid email address is required.', 'defender-security' ),
442 )
443 );
444 }
445
446 // Logging.
447 $message = sprintf(
448 'IP: %s, email: %s, service: %s, reason: %s',
449 $data['ip'],
450 $reporter_email,
451 $data['service'],
452 $data['reason']
453 );
454 $this->service->log_ip_message( $message );
455
456 // Track the request. Forced so it operates independently of tracking consent.
457 $this->forced_track(
458 'def_request_trusted_ip',
459 array(
460 'IP Address' => $data['ip'],
461 'User Email' => $reporter_email,
462 'Service' => $data['service'],
463 'Reason' => $data['reason'],
464 )
465 );
466
467 return new Response( true, array() );
468 }
469
470 /**
471 * Check if an IP exists in the Local, Custom and AntiBot blocklist.
472 *
473 * @param Request $request The HTTP request object.
474 *
475 * @defender_route
476 * @return Response
477 */
478 public function blocklist_checker_search( Request $request ): Response {
479 $data = $request->get_data(
480 array(
481 'ip' => array(
482 'type' => 'string',
483 'sanitize' => 'sanitize_text_field',
484 ),
485 )
486 );
487
488 if ( false === filter_var( $data['ip'], FILTER_VALIDATE_IP ) ) {
489 return new Response(
490 false,
491 array(
492 'success' => false,
493 'show_notice' => false,
494 'message' => __( 'Invalid IP address.', 'defender-security' ),
495 )
496 );
497 }
498
499 $bl_service = wd_di()->get( Blacklist_Lockout::class );
500 $gi_service = wd_di()->get( Global_IP::class );
501
502 $ip = $data['ip'];
503
504 $is_ip_allowed = $bl_service->is_ip_whitelisted( $ip )
505 || $bl_service->is_country_whitelist( $ip )
506 || ( $gi_service->is_global_ip_enabled() && $gi_service->is_ip_allowed( $ip ) );
507
508 // Fetch the latest Custom IP blocklist from the Hub so we don't rely on stale cached data.
509 $gi_service->fetch_global_ip_list();
510
511 return new Response(
512 true,
513 array(
514 'success' => true,
515 'show_notice' => false,
516 'message' => __( 'The IP address is searched successfully.', 'defender-security' ),
517 'local' => ! $is_ip_allowed && $bl_service->is_blacklist( $data['ip'] ),
518 'central' => ! $is_ip_allowed && $gi_service->is_global_ip_enabled() && $gi_service->is_ip_blocked( $data['ip'] ),
519 'antibot' => ! $is_ip_allowed && $this->service->is_ip_blocked( $ip ),
520 )
521 );
522 }
523
524 /**
525 * Add IP to AntiBot allowlist.
526 *
527 * @param Request $request The HTTP request object.
528 *
529 * @defender_route
530 * @return Response
531 */
532 public function blocklist_checker_add_ip_to_allowlist( Request $request ): Response {
533 $data = $request->get_data(
534 array(
535 'ip' => array(
536 'type' => 'string',
537 'sanitize' => 'sanitize_text_field',
538 ),
539 )
540 );
541 $ip = $data['ip'];
542 if ( false === filter_var( $ip, FILTER_VALIDATE_IP ) ) {
543 return new Response(
544 false,
545 array(
546 'message' => esc_html__( 'Invalid IP address.', 'defender-security' ),
547 )
548 );
549 }
550 $collection = 'allowlist';
551 $model = wd_di()->get( \WP_Defender\Model\Setting\Blacklist_Lockout::class );
552 $already_in_list = $model->is_ip_in_list( $ip, $collection );
553
554 // Add to Local allowlist.
555 if ( ! $already_in_list ) {
556 $model->add_to_list( $ip, $collection );
557 }
558 // Add to Custom IP allowlist.
559 $global_ip_service = wd_di()->get( Global_IP::class );
560 if ( ! $already_in_list && $global_ip_service->can_central_ip_autosync() ) {
561 $data = array(
562 'allow_list' => array( $ip ),
563 );
564
565 $res = $global_ip_service->add_to_global_ip_list( $data );
566 if ( is_wp_error( $res ) ) {
567 return new Response(
568 false,
569 array(
570 'message' => $res->get_error_message(),
571 )
572 );
573 }
574 }
575
576 $manage_link_open = '<a href="' . network_admin_url( 'admin.php?page=wdf-ip-lockout&view=blocklist#tab-ip-allowlist' ) . '">';
577 $manage_link_close = '</a>';
578
579 if ( $already_in_list ) {
580 $message = sprintf(
581 /* translators: 1: IP address. 2: Opening anchor tag. 3: Closing anchor tag. */
582 esc_html__(
583 'IP %1$s is already in your Site\'s allowlist. You can manage it in %2$sIP Lockouts%3$s.',
584 'defender-security'
585 ),
586 $ip,
587 $manage_link_open,
588 $manage_link_close
589 );
590 } else {
591 $message = sprintf(
592 /* translators: 1: IP address. 2: Opening anchor tag. 3: Closing anchor tag. */
593 esc_html__(
594 'IP %1$s has been added to your Site\'s allowlist. You can manage it in %2$sIP Lockouts%3$s.',
595 'defender-security'
596 ),
597 $ip,
598 $manage_link_open,
599 $manage_link_close
600 );
601 }
602
603 return new Response(
604 true,
605 array(
606 'message' => $message,
607 'already_in_list' => $already_in_list,
608 )
609 );
610 }
611
612 /**
613 * Sync AntiBot status with Hosting.
614 * If AntiBot is enabled in Hosting, update the 'managed_by' and 'enabled' status.
615 * If AntiBot is disabled in Hosting, do nothing.
616 *
617 * @return void
618 */
619 public function sync_state(): void {
620 $hosting_enabled = defender_get_hosting_feature_state( 'antibot' );
621
622 if (
623 true === $hosting_enabled
624 && 'plugin' === $this->model->managed_by
625 && true !== get_site_transient( Antibot_Global_Firewall_Component::IS_SWITCHING_TO_PLUGIN_IN_PROGRESS )
626 ) {
627 $this->model->enabled = false;
628 $this->model->managed_by = 'hosting';
629 $this->model->save();
630 }
631 }
632
633 /**
634 * Handle request to switch mode.
635 *
636 * @defender_route
637 * @return Response
638 */
639 public function switch_mode(): Response {
640 $result = $this->process_switch_mode();
641 if ( $result['success'] ) {
642 if ( $this->maybe_track() ) {
643 wd_di()->get( Antibot_Analytics::class )->track_antibot( false, 'Feature Page' );
644 }
645 return new Response( true, $result['data'] );
646 }
647
648 return new Response( false, $result['data'] );
649 }
650
651 /**
652 * Process request to switch mode.
653 *
654 * @return array
655 */
656 public function process_switch_mode(): array {
657 $result = $this->service->switch_mode();
658 if ( false !== $result && ! is_wp_error( $result ) ) {
659 return array(
660 'success' => true,
661 'data' => array(
662 'message' => sprintf(
663 /* translators: 1. Open tag. 2. Close tag. 3. Mode. */
664 __( '%1$sAntiBot Firewall%2$s mode updated – You\'re now using %3$s Mode.', 'defender-security' ),
665 '<strong>',
666 '</strong>',
667 ucfirst( $result )
668 ),
669 'mode' => $result,
670 'ips_count' => $this->service->get_blocklisted_ip_count(),
671 'auto_close' => true,
672 ),
673 );
674 }
675
676 return array(
677 'success' => false,
678 'data' => array(
679 'message' => __( 'Failed to switch. Please try again.', 'defender-security' ),
680 'auto_close' => true,
681 ),
682 );
683 }
684
685 /**
686 * Handle expired membership by automatically disabling the AntiBot Global Firewall module.
687 * Logs the action when the feature is disabled due to expired membership.
688 *
689 * @return void
690 */
691 public function handle_expired_membership(): void {
692 if ( $this->model->enabled && $this->is_expired_membership_type() ) {
693 $this->service->managed_by_plugin_action( false );
694 $this->log( 'AntiBot Global Firewall automatically disabled due to expired membership.', Antibot_Global_Firewall_Component::LOG_FILE_NAME );
695 }
696 }
697
698 /**
699 * Handle request to send feedback.
700 *
701 * @param Request $request The request object.
702 *
703 * @defender_route
704 * @return Response
705 */
706 public function send_feedback( Request $request ) {
707 $data = $request->get_data(
708 array(
709 'feedback' => array( 'type' => 'string' ),
710 )
711 );
712 if ( '' !== $data['feedback'] ) {
713 // Send feedback.
714 $this->track_feature(
715 'Antibot Survey',
716 array(
717 'Notice Action' => 'Share feedback',
718 'Feedback' => $data['feedback'],
719 )
720 );
721 }
722
723 $message = sprintf(
724 /* translators: 1: Open tag, 2: Close tag */
725 __( '%1$sThanks for your feedback!%2$s It helps us make Defender even better.', 'defender-security' ),
726 '<strong>',
727 '</strong>'
728 );
729
730 return new Response(
731 true,
732 array(
733 'message' => $message,
734 'auto_close' => true,
735 )
736 );
737 }
738 }
739