PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.1.0
Defender Security – Malware Scanner, Login Security & Firewall v6.1.0
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 / class-backup-settings.php

class-backup-settings.php in Defender Security – Malware Scanner, Login Security & Firewall 6.1.0, at src/component/class-backup-settings.php

1,426 lines 56.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles backup and restoration of settings.
4 *
5 * @package WP_Defender\Component
6 */
7
8 namespace WP_Defender\Component;
9
10 use Countable;
11 use WP_Defender\Component;
12 use WP_Defender\Traits\IP;
13 use WP_Defender\Behavior\WPMUDEV;
14 use WP_Defender\Controller\Blacklist;
15 use WP_Defender\Controller\Global_Ip;
16 use WP_Defender\Controller\Two_Factor;
17 use WP_Defender\Controller\Nf_Lockout;
18 use WP_Defender\Controller\Main_Setting;
19 use WP_Defender\Controller\Audit_Logging;
20 use WP_Defender\Controller\Security_Headers;
21 use WP_Defender\Controller\Blocklist_Monitor;
22 use WP_Defender\Model\Notification\Audit_Report;
23 use WP_Defender\Model\Setting\Global_Ip_Lockout;
24 use WP_Defender\Model\Setting\Scan as Model_Scan;
25 use WP_Defender\Controller\Scan as Controller_Scan;
26 use WP_Defender\Model\Notification\Malware_Report;
27 use WP_Defender\Model\Notification\Tweak_Reminder;
28 use WP_Defender\Model\Notification\Firewall_Report;
29 use WP_Defender\Component\Config\Config_Hub_Helper;
30 use WP_Defender\Model\Setting\Two_Fa as Model_Two_Fa;
31 use WP_Defender\Component\Security_Tweaks\Security_Key;
32 use WP_Defender\Model\Setting\Antibot_Global_Firewall_Setting;
33 use WP_Defender\Model\Notification\Malware_Notification;
34 use WP_Defender\Model\Setting\Firewall as Model_Firewall;
35 use WP_Defender\Model\Notification\Firewall_Notification;
36 use WP_Defender\Controller\Firewall as Controller_Firewall;
37 use WP_Defender\Model\Setting\Mask_Login as Model_Mask_Login;
38 use WP_Defender\Component\Security_Tweaks\Prevent_Enum_Users;
39 use WP_Defender\Controller\Mask_Login as Controller_Mask_Login;
40 use WP_Defender\Controller\UA_Lockout as Controller_Ua_Lockout;
41 use WP_Defender\Model\Setting\Main_Setting as Model_Main_Setting;
42 use WP_Defender\Model\Setting\Audit_Logging as Model_Audit_Logging;
43 use WP_Defender\Model\Setting\Login_Lockout as Model_Login_Lockout;
44 use WP_Defender\Model\Setting\User_Agent_Lockout as Model_Ua_Lockout;
45 use WP_Defender\Model\Setting\Strong_Password as Strong_Password_Model;
46 use WP_Defender\Model\Setting\Security_Tweaks as Model_Security_Tweaks;
47 use WP_Defender\Controller\Strong_Password as Strong_Password_Controller;
48 use WP_Defender\Controller\Security_Tweaks as Controller_Security_Tweaks;
49 use WP_Defender\Model\Setting\Notfound_Lockout as Model_Notfound_Lockout;
50 use WP_Defender\Model\Setting\Security_Headers as Model_Security_Headers;
51 use WP_Defender\Model\Setting\Blacklist_Lockout as Model_Blacklist_Lockout;
52 use WP_Defender\Model\Setting\Session_Protection as Session_Protection_Model;
53 use WP_Defender\Controller\Session_Protection as Session_Protection_Controller;
54 use WP_Defender\Model\Setting\Password_Protection as Model_Password_Protection;
55 use WP_Defender\Controller\Password_Protection as Controller_Password_Protection;
56
57 /**
58 * Handles backup and restoration of settings.
59 */
60 class Backup_Settings extends Component {
61
62 use IP;
63
64 public const KEY = 'defender_last_settings', INDEXER = 'defender_config_indexer';
65
66 /**
67 * Indicates whether the current installation is a pro version.
68 *
69 * @var bool
70 */
71 private $is_pro;
72
73 /**
74 * Indicates whether the site has a HUB API key (true for both free and pro with Hub connected).
75 *
76 * @var bool
77 */
78 private $has_hub_api_key;
79
80 /**
81 * Constructor for the Backup_Settings class.
82 * It initializes the class and sets whether the current installation is a pro version.
83 */
84 public function __construct() {
85 $wpmudev = new WPMUDEV();
86 $this->is_pro = $wpmudev->is_pro();
87 $this->has_hub_api_key = false !== $wpmudev->get_apikey();
88 }
89
90 /**
91 * Changes the format of subscribers in a notification object to an array format.
92 *
93 * @param object $notification_object The notification object to format.
94 *
95 * @return array Returns an array of subscribers formatted.
96 */
97 public function change_subscriber_format( $notification_object ): array {
98 if (
99 ! is_object( $notification_object )
100 && ! is_array( $notification_object->in_house_recipients )
101 && ! is_array( $notification_object->out_house_recipients )
102 ) {
103 return array();
104 }
105 if ( array() === $notification_object->in_house_recipients && array() === $notification_object->out_house_recipients ) {
106 return array();
107 }
108
109 $subscribers = array();
110 if ( array() !== $notification_object->in_house_recipients ) {
111 $subscribers['in_house_recipients'] = $notification_object->in_house_recipients;
112 }
113 if ( array() !== $notification_object->out_house_recipients ) {
114 $subscribers['out_house_recipients'] = $notification_object->out_house_recipients;
115 }
116
117 return $subscribers;
118 }
119
120 /**
121 * Gather settings from all modules.
122 *
123 * @return array
124 */
125 public function gather_data(): array {
126 $audit = array();
127 $tweak_class = wd_di()->get( Controller_Security_Tweaks::class );
128 $tweak_class->refresh_tweaks_status();
129 $settings = new Model_Security_Tweaks();
130 $tweak_notification = new Tweak_Reminder();
131 $security_tweaks = array(
132 'notification' => $tweak_notification->status,
133 'data' => $settings->data,
134 'fixed' => $settings->fixed,
135 'issues' => $settings->issues,
136 'ignore' => $settings->ignore,
137 'automate' => $settings->automate,
138 'enabled_user_enums' => $this->get_enabled_user_enums(),
139 'security_key' => $this->get_security_key_all_options(),
140 );
141 $settings = new Model_Scan();
142 $scan_report = new Malware_Report();
143 $scan_notification = new Malware_Notification();
144 $scan = array(
145 'integrity_check' => $settings->integrity_check,
146 'check_core' => $settings->check_core,
147 'check_plugins' => $settings->check_plugins,
148 'check_known_vuln' => $settings->check_known_vuln,
149 'scan_malware' => $settings->scan_malware,
150 'filesize' => $settings->filesize,
151 'check_abandoned_plugin' => $settings->check_abandoned_plugin,
152 // @since 2.7.0 changes for Scheduled options.
153 'scheduled_scanning' => $settings->scheduled_scanning,
154 'day' => $settings->day,
155 'day_n' => $settings->day_n,
156 'time' => $settings->time,
157 'frequency' => $settings->frequency,
158 'report' => $scan_report->status,
159 'always_send' => $scan_report->configs['always_send'],
160 'report_subscribers' => $this->change_subscriber_format( $scan_report ),
161 // @since 2.7.0 for backward compatibility. We can remove it in the next version.
162 'dry_run' => false,
163 'notification' => $scan_notification->status,
164 'always_send_notification' => $scan_notification->configs['always_send'],
165 'error_send' => $scan_notification->configs['error_send'],
166 'notification_subscribers' => $this->change_subscriber_format( $scan_notification ),
167 'email_subject_issue_not_found' => $scan_notification->configs['template']['not_found']['subject'],
168 'email_subject_issue_found' => $scan_notification->configs['template']['found']['subject'],
169 'email_subject_error' => $scan_notification->configs['template']['error']['subject'],
170 'email_content_issue_not_found' => $scan_notification->configs['template']['not_found']['body'],
171 'email_content_issue_found' => $scan_notification->configs['template']['found']['body'],
172 'email_content_error' => $scan_notification->configs['template']['error']['body'],
173 );
174 $audit['enabled'] = false;
175
176 $settings_firewall = new Model_Firewall();
177 $settings_ll = new Model_Login_Lockout();
178 $settings_nl = new Model_Notfound_Lockout();
179 $settings_bl = new Model_Blacklist_Lockout();
180 $lockout_notification = new Firewall_Notification();
181 $lockout_report = new Firewall_Report();
182 $ua_banning_model = new Model_Ua_Lockout();
183 $settings_gi = wd_di()->get( Global_Ip_Lockout::class );
184 $settings_antibot = wd_di()->get( Antibot_Global_Firewall_Setting::class );
185 $iplockout = array(
186 'login_protection' => $settings_ll->enabled,
187 'login_protection_login_attempt' => $settings_ll->attempt,
188 'login_protection_lockout_timeframe' => $settings_ll->timeframe,
189 'login_protection_lockout_ban' => $settings_ll->lockout_type,
190 'login_protection_lockout_duration' => $settings_ll->duration,
191 'login_protection_lockout_duration_unit' => $settings_ll->duration_unit,
192 'login_protection_lockout_message' => $settings_ll->lockout_message,
193 'username_blacklist' => $settings_ll->username_blacklist,
194 'detect_404' => $settings_nl->enabled,
195 'detect_404_threshold' => $settings_nl->attempt,
196 'detect_404_timeframe' => $settings_nl->timeframe,
197 'detect_404_lockout_ban' => $settings_nl->lockout_type,
198 'detect_404_lockout_duration' => $settings_nl->duration,
199 'detect_404_lockout_duration_unit' => $settings_nl->duration_unit,
200 'detect_404_lockout_message' => $settings_nl->lockout_message,
201 'detect_404_blacklist' => $settings_nl->blacklist,
202 'detect_404_whitelist' => $settings_nl->whitelist,
203 'detect_404_logged' => $settings_nl->detect_logged,
204 'ip_blacklist' => $settings_bl->ip_blacklist,
205 'ip_whitelist' => $settings_bl->ip_whitelist,
206 'country_blacklist' => $settings_bl->country_blacklist,
207 'country_whitelist' => $settings_bl->country_whitelist,
208 'ip_lockout_message' => $settings_bl->ip_lockout_message,
209 'login_lockout_notification' => $lockout_notification->configs['login_lockout'],
210 'ip_lockout_notification' => $lockout_notification->configs['nf_lockout'],
211 'notification' => $lockout_notification->status,
212 'notification_subscribers' => $this->change_subscriber_format( $lockout_notification ),
213 'cooldown_enabled' => $lockout_notification->configs['limit'],
214 'cooldown_number_lockout' => $lockout_notification->configs['threshold'],
215 'cooldown_period' => $lockout_notification->configs['cool_off'],
216 'report' => $lockout_report->status,
217 'report_subscribers' => $this->change_subscriber_format( $lockout_report ),
218 'report_frequency' => $lockout_report->frequency,
219 'day' => $lockout_report->day,
220 'day_n' => $lockout_report->day_n,
221 'report_time' => $lockout_report->time,
222 // @since 2.7.0 We can remove it in the next version.
223 'dry_run' => false,
224 'storage_days' => $settings_firewall->storage_days,
225 'geoIP_db' => $settings_bl->geodb_path,
226 'maxmind_license_key' => $settings_bl->maxmind_license_key,
227 'ip_blocklist_cleanup_interval' => $settings_firewall->ip_blocklist_cleanup_interval,
228 'ua_banning_enabled' => $ua_banning_model->enabled,
229 'ua_banning_message' => $ua_banning_model->message,
230 'ua_banning_blacklist' => $ua_banning_model->blacklist,
231 'ua_banning_whitelist' => $ua_banning_model->whitelist,
232 'ua_banning_empty_headers' => $ua_banning_model->empty_headers,
233 'global_ip_list' => $settings_gi->enabled,
234 'global_ip_list_blocklist_autosync' => $settings_gi->blocklist_autosync,
235 'antibot' => $settings_antibot->enabled,
236 );
237 $settings_two_fa = new Model_Two_Fa();
238 $settings_mask_login = new Model_Mask_Login();
239 $advanced_tools = array(
240 'two_factor' => $settings_two_fa->export(),
241 'mask_login' => $settings_mask_login->export(),
242 );
243 $settings_main = new Model_Main_Setting();
244 $main_settings = $settings_main->export();
245 $settings_sec_headers = new Model_Security_Headers();
246 $security_headers = $settings_sec_headers->export();
247 $ret = array(
248 'scan' => $scan,
249 'iplockout' => $iplockout,
250 'two_factor' => $advanced_tools['two_factor'],
251 'mask_login' => $advanced_tools['mask_login'],
252 'settings' => $main_settings,
253 'security_headers' => $security_headers,
254 );
255 if ( isset( $audit ) ) {
256 $ret['audit'] = $audit;
257 }
258 if ( $this->has_hub_api_key ) {
259 $blocklist_monitor_class = wd_di()->get( Blocklist_Monitor::class );
260 $status = (string) $blocklist_monitor_class->get_status();
261 $ret['blocklist_monitor']['enabled'] = '1' === $status;
262 $ret['blocklist_monitor']['status'] = $status;
263 } else {
264 $ret['blocklist_monitor']['enabled'] = false;
265 $ret['blocklist_monitor']['status'] = '-1';
266 }
267 // For Pwned passwords.
268 $pwned_password_model = wd_di()->get( Model_Password_Protection::class );
269 $ret['pwned_passwords']['enabled'] = $pwned_password_model->is_active();
270 $ret['pwned_passwords']['user_roles'] = $pwned_password_model->user_roles;
271 $ret['pwned_passwords']['custom_message'] = $pwned_password_model->pwned_actions['force_change_message'];
272 // For Strong Passwords.
273 $strong_password_model = wd_di()->get( Strong_Password_Model::class );
274 $ret['force_strong_password']['enabled'] = $strong_password_model->is_active();
275 $ret['force_strong_password']['user_roles'] = $strong_password_model->user_roles;
276 $ret['force_strong_password']['message'] = $strong_password_model->get_message();
277 // For Session Protection.
278 $session_protection_model = wd_di()->get( Session_Protection_Model::class );
279 $ret['session_protection']['enabled'] = $session_protection_model->enabled;
280 $ret['session_protection']['user_roles'] = $session_protection_model->user_roles;
281 $ret['session_protection']['lock_properties'] = $session_protection_model->lock_properties;
282 $ret['session_protection']['idle_timeout'] = $session_protection_model->idle_timeout;
283 $ret['session_protection']['login_duration'] = $session_protection_model->login_duration;
284 // It's better to add Tweaks as the last key to eliminate unexpected behavior with possible user logout.
285 $ret['security_tweaks'] = $security_tweaks;
286
287 return $ret;
288 }
289
290 /**
291 * Retrieves all configurations from the options table.
292 *
293 * @return array Returns an array of all configurations.
294 */
295 public function get_configs(): array {
296 $keys = get_site_option( self::INDEXER, false );
297 $results = array();
298 if ( ! is_array( $keys ) || array() === $keys ) {
299 return $results;
300 }
301
302 foreach ( $keys as $key ) {
303 $config = get_site_option( $key );
304
305 if ( false === $config ) {
306 $this->remove_index( $key );
307 } else {
308 $results[ $key ] = $config;
309 }
310 }
311
312 return $results;
313 }
314
315 /**
316 * Sets a specific configuration as active.
317 *
318 * @param string $key The key of the configuration to make active.
319 */
320 public function make_config_active( string $key ): void {
321 $configs = $this->get_configs();
322 foreach ( $configs as $k => $config ) {
323 if ( $k === $key ) {
324 $config['is_active'] = true;
325 } else {
326 $config['is_active'] = false;
327 }
328 update_site_option( $k, $config );
329 }
330 }
331
332 /**
333 * Clear keys if the last config was deleted.
334 *
335 * @return void
336 */
337 public function clear_keys(): void {
338 $keys = get_site_option( self::INDEXER, array() );
339 if ( array() === $keys ) {
340 delete_site_option( self::INDEXER );
341 delete_site_option( self::KEY );
342 }
343 }
344
345 /**
346 * Clears all configurations from the options table.
347 */
348 public function clear_configs(): void {
349 $keys = get_site_option( self::INDEXER, false );
350 if ( is_array( $keys ) && array() !== $keys ) {
351 foreach ( $keys as $key ) {
352 delete_site_option( $key );
353 }
354 }
355 delete_site_option( self::INDEXER );
356 delete_site_option( self::KEY );
357 delete_site_transient( Config_Hub_Helper::CONFIGS_TRANSIENT_KEY );
358 }
359
360 /**
361 * Do the deletion only if there are no indexed configs, so as not to delete the displayed configs.
362 */
363 protected function remove_unindexed_configs() {
364 global $wpdb;
365
366 $result = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
367 $wpdb->prepare(
368 "SELECT option_name FROM {$wpdb->prefix}options WHERE option_name LIKE %s",
369 '%wp_defender_config_%'
370 ),
371 ARRAY_A
372 );
373 if ( array() !== $result ) {
374 foreach ( $result as $arr ) {
375 delete_site_option( $arr['option_name'] );
376 }
377 }
378 }
379
380 /**
381 * Create a default config.
382 *
383 * @return void
384 */
385 public function maybe_create_default_config(): void {
386 $keys = get_site_option( self::INDEXER, array() );
387 if ( array() === $keys ) {
388 $this->remove_unindexed_configs();
389 $key = 'wp_defender_config_default' . time();
390 $config = get_site_option( $key );
391 if ( ! is_array( $config ) || array() === $config ) {
392 $this->create_basic_config( $key );
393 }
394 }
395 }
396
397 /**
398 * Creates a basic configuration and saves it to the options table.
399 *
400 * @param string $key The key under which to save the configuration.
401 *
402 * @return void
403 * @since 2.6.5 Reduce differences between Basic and Default security configs.
404 */
405 private function create_basic_config( string $key ): void {
406 $configs = array();
407 // @since 2.6.5 Clear recipients.
408 $default_recipients = array();
409 // Init Tweaks class and refresh status.
410 $tweak_class = wd_di()->get( Controller_Security_Tweaks::class );
411 $tweak_class->refresh_tweaks_status();
412 // Get user role keys.
413 $user_roles = array_keys( get_editable_roles() );
414 // Default values.
415 $default_scan_notification_values = ( new Malware_Notification() )->get_default_values();
416 $default_login_lockout_values = ( new Model_Login_Lockout() )->get_default_values();
417 $default_404_lockout_values = ( new Model_Notfound_Lockout() )->get_default_values();
418 $default_ip_lockout_values = ( new Model_Blacklist_Lockout() )->get_default_values();
419 $default_ua_lockout_values = ( new Model_Ua_Lockout() )->get_default_values();
420 $default_password_protection_values = ( new Model_Password_Protection() )->get_default_values();
421 $default_2fa_values = ( new Model_Two_Fa() )->get_default_values();
422 $default_scan_settings = wd_di()->get( Model_Scan::class );
423 // Total data.
424 $data = array(
425 'scan' => array(
426 'integrity_check' => true,
427 'check_core' => true,
428 'check_plugins' => true,
429 'check_known_vuln' => $default_scan_settings->check_known_vuln,
430 'scan_malware' => $default_scan_settings->scan_malware,
431 'check_abandoned_plugin' => true,
432 'filesize' => 3,
433 'report' => 'enabled',
434 'always_send' => false,
435 'report_subscribers' => $default_recipients,
436 'day' => 'sunday',
437 'day_n' => 1,
438 'time' => '4:00',
439 'frequency' => 'weekly',
440 // @since 2.7.0 We can remove it in the next version.
441 'dry_run' => false,
442 'notification' => 'enabled',
443 'always_send_notification' => false,
444 'error_send' => false,
445 'notification_subscribers' => $default_recipients,
446 'email_subject_issue_found' => $default_scan_notification_values['subject_issue_found'],
447 'email_subject_issue_not_found' => $default_scan_notification_values['subject_issue_not_found'],
448 'email_subject_error' => $default_scan_notification_values['subject_error'],
449 'email_content_issue_found' => $default_scan_notification_values['content_issue_found'],
450 'email_content_issue_not_found' => $default_scan_notification_values['content_issue_not_found'],
451 'email_content_error' => $default_scan_notification_values['content_error'],
452 // Values for frequency, day and time are above.
453 'scheduled_scanning' => true,
454 ),
455 'iplockout' => array(
456 'login_protection' => true,
457 'login_protection_login_attempt' => '5',
458 'login_protection_lockout_timeframe' => '300',
459 'login_protection_lockout_ban' => 'timeframe',
460 'login_protection_lockout_duration' => '4',
461 'login_protection_lockout_duration_unit' => 'hours',
462 'login_protection_lockout_message' => $default_login_lockout_values['message'],
463 // @since 2.7.0 New default blocklisted usernames.
464 'username_blacklist' => "adm\nadmin\nadmin1\nhostname\nmanager\nqwerty\nroot\nsupport\nsysadmin\ntest\nuser\nadministrator",
465 'detect_404' => true,
466 'detect_404_threshold' => '20',
467 'detect_404_timeframe' => '300',
468 'detect_404_lockout_ban' => 'timeframe',
469 'detect_404_lockout_duration' => '4',
470 'detect_404_lockout_duration_unit' => 'hours',
471 'detect_404_lockout_message' => $default_404_lockout_values['message'],
472 'detect_404_blacklist' => '',
473 // @since 2.7.0 New default whitelisted extensions.
474 'detect_404_whitelist' => ".css\n.js\n.jpg\n.png\n.gif\n.map",
475 'detect_404_logged' => true,
476 'ip_blacklist' => '',
477 // @since 2.6.5 Clear the current user IP.
478 'ip_whitelist' => '',
479 'country_blacklist' => '',
480 'country_whitelist' => '',
481 'ip_lockout_message' => $default_ip_lockout_values['message'],
482 'login_lockout_notification' => true,
483 'ip_lockout_notification' => true,
484 'notification' => 'enabled',
485 'notification_subscribers' => $default_recipients,
486 'cooldown_enabled' => false,
487 'cooldown_number_lockout' => '3',
488 'cooldown_period' => '24',
489 'report' => 'enabled',
490 'report_subscribers' => $default_recipients,
491 'report_frequency' => 'weekly',
492 'day' => 'sunday',
493 'day_n' => 1,
494 'report_time' => '4:00',
495 // @since 2.7.0 We can remove it in the next version.
496 'dry_run' => false,
497 'storage_days' => '180',
498 'geoIP_db' => '',
499 'ip_blocklist_cleanup_interval' => 'never',
500 'ua_banning_enabled' => false,
501 'ua_banning_message' => $default_ua_lockout_values['message'],
502 'ua_banning_blacklist' => $default_ua_lockout_values['blacklist'],
503 'ua_banning_whitelist' => $default_ua_lockout_values['whitelist'],
504 'ua_banning_empty_headers' => false,
505 // @since 2.7.1 New key of Blacklist_Lockout.
506 'maxmind_license_key' => '',
507 // @since 3.4.0 New Global IP list.
508 'global_ip_list' => false,
509 'global_ip_list_blocklist_autosync' => false,
510 ),
511 'two_factor' => array(
512 // @since 2.6.5 Disabled module.
513 'enabled' => false,
514 'lost_phone' => true,
515 'force_auth' => false,
516 'force_auth_mess' => $default_2fa_values['message'],
517 'user_roles' => $user_roles,
518 'force_auth_roles' => array(),
519 'custom_graphic' => false,
520 'custom_graphic_type' => Model_Two_Fa::CUSTOM_GRAPHIC_TYPE_UPLOAD,
521 'custom_graphic_url' => '',
522 'custom_graphic_link' => '',
523 'email_subject' => $default_2fa_values['email_subject'],
524 'email_sender' => $default_2fa_values['email_sender'],
525 'email_body' => $default_2fa_values['email_body'],
526 'app_title' => $default_2fa_values['app_title'],
527 ),
528 'mask_login' => array(
529 'enabled' => false,
530 'mask_url' => '',
531 'redirect_traffic' => 'off',
532 'redirect_traffic_url' => '',
533 'redirect_traffic_page_id' => 0,
534 ),
535 'security_headers' => array(
536 'sh_xframe' => true,
537 'sh_xframe_mode' => 'sameorigin',
538 'sh_xframe_urls' => '',
539 'sh_xss_protection' => true,
540 'sh_xss_protection_mode' => 'sanitize',
541 'sh_content_type_options' => true,
542 'sh_content_type_options_mode' => 'nosniff',
543 'sh_strict_transport' => true,
544 'hsts_preload' => false,
545 'include_subdomain' => false,
546 'hsts_cache_duration' => '30 days',
547 'sh_referrer_policy' => true,
548 'sh_referrer_policy_mode' => 'origin-when-cross-origin',
549 'sh_feature_policy' => true,
550 'sh_feature_policy_mode' => 'self',
551 'sh_feature_policy_urls' => '',
552 ),
553 'settings' => array(
554 'uninstall_data' => 'keep',
555 'uninstall_settings' => 'preserve',
556 // @since 2.7.0 Empty.
557 'translate' => '',
558 'usage_tracking' => false,
559 'high_contrast_mode' => false,
560 ),
561 'pwned_passwords' => array(
562 'enabled' => false,
563 'user_roles' => $user_roles,
564 'custom_message' => $default_password_protection_values['message'],
565 ),
566 'force_strong_password' => array(
567 'enabled' => false,
568 'user_roles' => $user_roles,
569 'message' => ( new Strong_Password_Model() )->get_message(),
570 ),
571 'session_protection' => array(
572 'enabled' => false,
573 'idle_timeout' => 1,
574 'login_duration' => ( new Session_Protection_Model() )->get_default_duration(),
575 'user_roles' => $user_roles,
576 'lock_properties' => array(),
577 ),
578 );
579 $data['audit']['enabled'] = false;
580 if ( $this->has_hub_api_key ) {
581 $data['blocklist_monitor'] = array(
582 // @since 2.7.0 Enable.
583 'enabled' => true,
584 'status' => '1',
585 );
586 } else {
587 $data['blocklist_monitor'] = array(
588 'enabled' => false,
589 'status' => '-1',
590 );
591 }
592 // It's better to add Tweaks as the last key to eliminate unexpected behavior with possible user logout.
593 $data['security_tweaks'] = array(
594 'notification' => 'disabled',
595 'data' => array(),
596 // @since 2.7.0 Specific values for 4 fixed, 0 ignored and 8 actioned tweaks. 12 tweaks in total.
597 'fixed' => array(
598 'disable-xml-rpc',
599 'login-duration',
600 'disable-trackback',
601 'prevent-enum-users',
602 ),
603 'issues' => array(
604 'php-version',
605 'wp-version',
606 'prevent-php-executed',
607 'protect-information',
608 'replace-admin-username',
609 'security-key',
610 'disable-file-editor',
611 'hide-error',
612 ),
613 'ignore' => array(),
614 );
615
616 $configs['configs'] = $data;
617 $configs['strings'] = $this->create_default_module_strings( $data );
618 $configs['name'] = esc_html__( 'Basic Config', 'defender-security' );
619 $configs['description'] = esc_html__( 'Recommended default protection for every site', 'defender-security' );
620 $configs['immortal'] = true;
621 $configs['is_removable'] = true;
622 $configs['version'] = 2;
623 update_site_option( $key, $configs );
624 $this->index_key( $key );
625 }
626
627 /**
628 * Adds a configuration key to the indexer in the options table.
629 *
630 * @param string $key The configuration key to index.
631 */
632 public function index_key( string $key ): void {
633 $keys = get_site_option( self::INDEXER, array() );
634 // Check for uniqueness.
635 if ( is_array( $keys ) ) {
636 $keys[ $key ] = $key;
637 $keys = array_unique( $keys );
638 update_site_option( self::INDEXER, $keys );
639 } elseif ( '' === $keys ) {
640 // The first config.
641 update_site_option( self::INDEXER, array( $key => $key ) );
642 }
643 }
644
645 /**
646 * Removes a configuration key from the indexer in the options table.
647 *
648 * @param string $key The configuration key to remove from the index.
649 */
650 public function remove_index( string $key ): void {
651 $keys = get_site_option( self::INDEXER, false );
652 unset( $keys[ array_search( $key, $keys, true ) ] );
653 update_site_option( self::INDEXER, $keys );
654 }
655
656 /**
657 * Backup the previous data before we process new version.
658 *
659 * @return array
660 */
661 public function backup_data(): array {
662 $data = $this->get_prev_settings();
663 $old_backup = get_site_option( self::KEY );
664 if ( ! is_array( $old_backup ) ) {
665 $old_backup = array();
666 }
667 if ( count( $old_backup ) > 20 ) {
668 // Remove the oldest key.
669 $old_backup = array_shift( $old_backup );
670 }
671 $version = get_site_option( 'wd_db_version' );
672 $old_backup[ $version . '_' . time() ] = $data;
673 update_site_option( self::KEY, $old_backup );
674
675 return $data;
676 }
677
678 /**
679 * Restores data from a backup.
680 *
681 * @param array $data The data to restore.
682 * @param string $request_reason The reason for the data restoration.
683 *
684 * @return bool|string Returns true if re-authentication is needed, otherwise false.
685 */
686 public function restore_data( array $data, string $request_reason = 'plugin' ) {
687 $need_reauth = false;
688 foreach ( $data as $module => $module_data ) {
689 if ( ! is_array( $module_data ) ) {
690 continue;
691 }
692
693 $controller = $this->module_to_controller( $module, true );
694 // Return array of objects if the module is IP Lockout.
695 if ( is_object( $controller ) || is_array( $controller ) ) {
696 foreach ( $module_data as &$value ) {
697 if ( isset( $value ) && ! is_array( $value ) && ! filter_var( $value, FILTER_VALIDATE_BOOLEAN ) ) {
698 $value = str_replace( '{nl}', PHP_EOL, $value );
699 }
700 }
701
702 /**
703 * Import the data and perform separate actions for each of the main modules:
704 * Scan, Firewall, Audit, Block list Monitor, Pwned Passwords,
705 * 2FA, Mask Login, Security Headers, Main settings, Tweaks.
706 */
707 if ( 'scan' === $module ) {
708 // Import.
709 $controller->import_data( $module_data );
710
711 $scan_notification = new Malware_Notification();
712 $scan_report = new Malware_Report();
713 if ( array() !== $module_data ) {
714 $scan_settings = new Model_Scan();
715 // For Scan notification.
716 if ( isset( $module_data['notification'] ) ) {
717 if ( $scan_notification->status !== $module_data['notification'] ) {
718 $scan_notification->status = $module_data['notification'];
719 }
720 if ( isset( $module_data['always_send_notification'] ) ) {
721 $scan_notification->configs['always_send'] = $module_data['always_send_notification'];
722 }
723 if ( isset( $module_data['error_send'] ) ) {
724 $scan_notification->configs['error_send'] = $module_data['error_send'];
725 }
726 if ( isset( $module_data['email_subject_issue_found'] ) && '' !== $module_data['email_subject_issue_found'] ) {
727 $scan_notification->configs['template']['found']['subject'] = $module_data['email_subject_issue_found'];
728 }
729 if ( isset( $module_data['email_subject_issue_not_found'] ) && '' !== $module_data['email_subject_issue_not_found'] ) {
730 $scan_notification->configs['template']['not_found']['subject'] = $module_data['email_subject_issue_not_found'];
731 }
732 if ( isset( $module_data['email_subject_error'] ) && '' !== $module_data['email_subject_error'] ) {
733 $scan_notification->configs['template']['error']['subject'] = $module_data['email_subject_error'];
734 }
735 if ( isset( $module_data['email_content_issue_found'] ) && '' !== $module_data['email_content_issue_found'] ) {
736 $scan_notification->configs['template']['found']['body'] = $module_data['email_content_issue_found'];
737 }
738 if ( isset( $module_data['email_content_issue_not_found'] ) && '' !== $module_data['email_content_issue_not_found'] ) {
739 $scan_notification->configs['template']['not_found']['body'] = $module_data['email_content_issue_not_found'];
740 }
741 if ( isset( $module_data['email_content_error'] ) && '' !== $module_data['email_content_error'] ) {
742 $scan_notification->configs['template']['error']['body'] = $module_data['email_content_error'];
743 }
744 if (
745 isset( $module_data['notification_subscribers'] )
746 && is_array( $module_data['notification_subscribers'] )
747 && array() !== $module_data['notification_subscribers']
748 ) {
749 // Reset all recipients before.
750 $scan_notification->in_house_recipients = array();
751 $scan_notification->out_house_recipients = array();
752 foreach ( $module_data['notification_subscribers'] as $key => $subscribers ) {
753 $scan_notification->$key = $subscribers;
754 }
755 }
756 $scan_notification->save();
757 }
758 if ( isset( $module_data['always_send'] ) ) {
759 $scan_report->configs['always_send'] = $module_data['always_send'];
760 }
761 if ( isset( $module_data['report_subscribers'] ) && is_array( $module_data['report_subscribers'] ) && array() !== $module_data['report_subscribers'] ) {
762 // Reset all recipients before.
763 $scan_report->in_house_recipients = array();
764 $scan_report->out_house_recipients = array();
765 foreach ( $module_data['report_subscribers'] as $key => $subscribers ) {
766 $scan_report->$key = $subscribers;
767 }
768 }
769 // Step#2 if 'scheduled_scanning'-key exists.
770 if ( isset( $module_data['scheduled_scanning'] ) ) {
771 $scan_settings->scheduled_scanning = $module_data['scheduled_scanning'];
772 }
773 // We update the values in Model_Scan and Malware_Report so that the values are synchronized in both models.
774 if ( isset( $module_data['frequency'] ) ) {
775 $scan_settings->frequency = $module_data['frequency'];
776 $scan_report->frequency = $module_data['frequency'];
777 }
778 if ( isset( $module_data['day'] ) ) {
779 $scan_settings->day = $module_data['day'];
780 $scan_report->day = $module_data['day'];
781 }
782 if ( isset( $module_data['day_n'] ) ) {
783 $scan_settings->day_n = (int) $module_data['day_n'];
784 $scan_report->day_n = (int) $module_data['day_n'];
785 }
786 if ( isset( $module_data['time'] ) ) {
787 $scan_settings->time = $module_data['time'];
788 $scan_report->time = $module_data['time'];
789 }
790 $scan_report->save();
791 $scan_settings->save();
792 } else {
793 // Default data for scan notification.
794 $scan_notification->status = 'disabled';
795 // @since 2.7.0 We can remove it in the next version.
796 $scan_notification->dry_run = false;
797 $scan_notification->save();
798 // For scan report.
799 $scan_report->status = 'disabled';
800 // @since 2.7.0 We can remove it in the next version.
801 $scan_report->dry_run = false;
802 $scan_report->save();
803 }
804 } elseif ( 'iplockout' === $module ) {
805 // Run Lockout submodules.
806 foreach ( $controller as $lockout_controller ) {
807 $lockout_controller->import_data( $module_data );
808 }
809 // For Notification and Report.
810 if ( is_array( $module_data ) && array() !== $module_data ) {
811 // Get string values for notification & report.
812 if ( isset( $module_data['notification'] ) ) {
813 $lockout_notification = new Firewall_Notification();
814 if ( $lockout_notification->status !== $module_data['notification'] ) {
815 $lockout_notification->status = $module_data['notification'];
816 }
817 if ( isset( $module_data['login_lockout_notification'] ) ) {
818 $lockout_notification->configs['login_lockout'] = $module_data['login_lockout_notification'];
819 }
820 if ( isset( $module_data['ip_lockout_notification'] ) ) {
821 $lockout_notification->configs['nf_lockout'] = $module_data['ip_lockout_notification'];
822 }
823 if ( isset( $module_data['cooldown_enabled'] ) ) {
824 $lockout_notification->configs['limit'] = $module_data['cooldown_enabled'];
825 }
826 if ( isset( $module_data['cooldown_number_lockout'] ) ) {
827 $lockout_notification->configs['threshold'] = $module_data['cooldown_number_lockout'];
828 }
829 if ( isset( $module_data['cooldown_period'] ) ) {
830 $lockout_notification->configs['cool_off'] = $module_data['cooldown_period'];
831 }
832 if ( isset( $module_data['notification_subscribers'] ) && is_array( $module_data['notification_subscribers'] ) && array() !== $module_data['notification_subscribers'] ) {
833 // Reset all recipients before.
834 $lockout_notification->in_house_recipients = array();
835 $lockout_notification->out_house_recipients = array();
836 foreach ( $module_data['notification_subscribers'] as $key => $subscribers ) {
837 $lockout_notification->$key = $subscribers;
838 }
839 }
840
841 $lockout_notification->save();
842 }
843 } else {
844 // Default data for lockout notification.
845 $lockout_notification = new Firewall_Notification();
846 $lockout_notification->status = 'disabled';
847 // @since 2.7.0 We can remove it in the next version.
848 $lockout_notification->dry_run = false;
849 $lockout_notification->configs = array(
850 'login_lockout' => false,
851 'nf_lockout' => false,
852 'limit' => false,
853 'threshold' => 3,
854 'cool_off' => 24,
855 );
856 $lockout_notification->save();
857 // For lockout report.
858 $lockout_report = new Firewall_Report();
859 $lockout_report->status = 'disabled';
860 // @since 2.7.0 We can remove it in the next version.
861 $lockout_report->dry_run = false;
862 $lockout_report->frequency = 'weekly';
863 $lockout_report->day_n = 1;
864 $lockout_report->day = 'sunday';
865 $lockout_report->time = '4:00';
866 $lockout_report->save();
867 }
868 } elseif ( 'audit' === $module ) {
869 // Import.
870 $controller->import_data( $module_data );
871 // Report.
872 $audit_report = new Audit_Report();
873 if ( is_array( $module_data ) && array() !== $module_data ) {
874 if ( isset( $module_data['frequency'] ) ) {
875 $audit_report->frequency = $module_data['frequency'];
876 }
877 if ( isset( $module_data['day_n'] ) ) {
878 $audit_report->day_n = (int) $module_data['day_n'];
879 }
880 if ( isset( $module_data['day'] ) ) {
881 $audit_report->day = $module_data['day'];
882 }
883 if ( isset( $module_data['time'] ) ) {
884 $audit_report->time = $module_data['time'];
885 }
886 if ( isset( $module_data['subscribers'] ) && is_array( $module_data['subscribers'] ) && array() !== $module_data['subscribers'] ) {
887 // Reset all recipients before.
888 $audit_report->in_house_recipients = array();
889 $audit_report->out_house_recipients = array();
890 foreach ( $module_data['subscribers'] as $key => $subscribers ) {
891 $audit_report->$key = $subscribers;
892 }
893 }
894 if ( isset( $module_data['last_sent'] ) ) {
895 $audit_report->last_sent = $module_data['last_sent'];
896 }
897 // @since 2.7.0 Remove 'dry_run'-restoring.
898 } else {
899 // For audit report.
900 $audit_report->status = 'disabled';
901 // @since 2.7.0 We can remove it in the next version.
902 $audit_report->dry_run = false;
903 $audit_report->frequency = 'weekly';
904 $audit_report->day_n = 1;
905 $audit_report->day = 'sunday';
906 $audit_report->time = '4:00';
907 }
908
909 $audit_report->save();
910 /**
911 * If 'blocklist_monitor' module is activated, 'enabled' set as true.
912 */
913 } elseif (
914 'blocklist_monitor' === $module
915 && $this->has_hub_api_key
916 ) {
917 // No need to import data. Just change status.
918 $blocklist_status = $module_data['status'] ?? '-1';
919 ( new Blocklist_Monitor() )->change_status( $blocklist_status );
920 } elseif (
921 'pwned_passwords' === $module
922 && isset( $module_data['custom_message'] )
923 ) {
924 $controller->import_data( $module_data );
925 } elseif ( 'force_strong_password' === $module ) {
926 $controller->import_data( $module_data );
927 } elseif ( 'session_protection' === $module ) {
928 $controller->import_data( $module_data );
929 } elseif ( 'two_factor' === $module ) {
930 $controller->import_data( $module_data );
931 } elseif ( 'mask_login' === $module ) {
932 $controller->import_data( $module_data );
933 } elseif ( 'security_headers' === $module ) {
934 $controller->import_data( $module_data );
935 } elseif ( 'settings' === $module ) {
936 $controller->import_data( $module_data );
937 } elseif ( 'security_tweaks' === $module ) {
938 // Import.
939 $controller->import_data( $module_data );
940 // Automate process.
941 if ( 'migration' !== $request_reason ) {
942 // There is some tweaks that require re-login. Not display error message during since 2.8.1 because it breaks the config flow on the Hub.
943 // If this is combined with mask login, then we need to redirect to new URL.
944 // The automate function should return this.
945 $tweak_class = wd_di()->get( Controller_Security_Tweaks::class );
946 $need_reauth = $tweak_class->automate( $module_data, $request_reason );
947 }
948 // For Tweak notification.
949 if ( is_array( $module_data ) && array() !== $module_data && isset( $module_data['notification'] ) ) {
950 $tweak_notification = new Tweak_Reminder();
951 if ( $tweak_notification->status !== $module_data['notification'] ) {
952 $tweak_notification->status = 'disabled';
953 }
954 $tweak_notification->save();
955 }
956 }
957 }
958 }
959 // We should disable quick setup.
960 update_site_option( 'wp_defender_is_activated', 1 );
961
962 return $need_reauth;
963 }
964
965 /**
966 * Parses data for import, preparing it for use in the system.
967 *
968 * @param null|array $configs Optional. The configurations to parse. If not provided, gathers current data.
969 *
970 * @return array Returns an array containing parsed data for import.
971 */
972 public function parse_data_for_import( $configs = null ): array {
973 if ( ! is_array( $configs ) || array() === $configs ) {
974 $configs = $this->gather_data();
975 }
976 $strings = array();
977 foreach ( $configs as $module => $module_data ) {
978 $controller = $this->module_to_controller( $module, false );
979 if ( ! is_object( $controller ) ) {
980 // In free, when audit not present.
981 $strings[ $module ][] = sprintf(
982 /* translators: %s: Html for Pro-tag. */
983 esc_html__( 'Inactive %s', 'defender-security' ),
984 '<span class="sui-tag sui-tag-pro">Pro</span>'
985 );
986 continue;
987 }
988 $strings[ $module ] = $controller->export_strings();
989 }
990
991 return array(
992 'configs' => $configs,
993 'strings' => $strings,
994 );
995 }
996
997 /**
998 * Converts a module name to its corresponding controller object.
999 *
1000 * @param string $module The name of the module.
1001 * @param bool $all_firewall_lockouts Whether to return all firewall and lockout controllers.
1002 *
1003 * @return object|array|string Returns the corresponding controller object, an array of controllers, or an empty
1004 * string if not applicable.
1005 */
1006 public function module_to_controller( string $module, bool $all_firewall_lockouts = false ) {
1007 switch ( $module ) {
1008 case 'security_tweaks':
1009 return new Controller_Security_Tweaks();
1010 case 'scan':
1011 return new Controller_Scan();
1012 case 'audit':
1013 return class_exists( Audit_Logging::class ) ? new Audit_Logging() : '';
1014 case 'iplockout':
1015 if ( $all_firewall_lockouts ) {
1016 return array(
1017 new Controller_Firewall(),
1018 new \WP_Defender\Controller\Login_Lockout(),
1019 new Nf_Lockout(),
1020 new Global_Ip(),
1021 new Blacklist(),
1022 new Controller_Ua_Lockout(),
1023 wd_di()->get( \WP_Defender\Controller\Antibot_Global_Firewall::class ),
1024 );
1025 } else {
1026 return new Controller_Firewall();
1027 }
1028 case 'settings':
1029 return new Main_Setting();
1030 case 'two_factor':
1031 return new Two_Factor();
1032 case 'mask_login':
1033 return new Controller_Mask_Login();
1034 case 'security_headers':
1035 return new Security_Headers();
1036 case 'blocklist_monitor':
1037 return new Blocklist_Monitor();
1038 case 'pwned_passwords':
1039 return new Controller_Password_Protection();
1040 case 'force_strong_password':
1041 return new Strong_Password_Controller();
1042 case 'session_protection':
1043 return new Session_Protection_Controller();
1044 default:
1045 return '';
1046 }
1047 }
1048
1049 /**
1050 * Checks if the provided configuration data has the new structure.
1051 *
1052 * @param array $configs The configuration data to check.
1053 *
1054 * @return bool Returns true if the new structure is used, otherwise false.
1055 */
1056 public function check_for_new_structure( array $configs ): bool {
1057 return isset( $configs['version'] ) ||
1058 ( isset( $configs['security_tweaks'] ) && array_key_exists( 'enabled_user_enums', $configs['security_tweaks'] ) );
1059 }
1060
1061 /**
1062 * Verifies the integrity of configuration data.
1063 *
1064 * @param array $data The configuration data to verify.
1065 *
1066 * @return bool Returns true if the data is valid, otherwise false.
1067 */
1068 public function verify_config_data( $data ): bool {
1069 if (
1070 ! isset( $data['name'], $data['configs'], $data['strings'] )
1071 || '' === $data['name'] || ! is_array( $data['strings'] ) || array() === $data['strings']
1072 ) {
1073 return false;
1074 }
1075
1076 return true;
1077 }
1078
1079 /**
1080 * Imports module strings for use in the system.
1081 *
1082 * @param array $data The data containing module strings to import.
1083 *
1084 * @return array Returns an array of imported module strings.
1085 */
1086 public function import_module_strings( array $data ): array {
1087 if ( ! isset( $data['strings'] ) || ! is_array( $data['strings'] ) || array() === $data['strings'] ) {
1088 return array();
1089 }
1090
1091 foreach ( $data['configs'] as $key => $config ) {
1092 if ( 'security_tweaks' === $key ) {
1093 $data['strings']['security_tweaks'] = wd_di()->get( Controller_Security_Tweaks::class )->config_strings( $config );
1094 } elseif ( 'scan' === $key ) {
1095 $data['strings']['scan'] = wd_di()->get( Controller_Scan::class )->config_strings( $config );
1096 } elseif ( 'iplockout' === $key ) {
1097 $data['strings']['iplockout'] = wd_di()->get( Controller_Firewall::class )->config_strings( $config );
1098 } elseif ( 'audit' === $key ) {
1099 // Additional check for Free version.
1100 $config = is_array( $config ) ? $config : array();
1101 $data['strings']['audit'] = wd_di()->get( Audit_Logging::class )->config_strings( $config );
1102 } elseif ( 'blocklist_monitor' === $key ) {
1103 // Additional check for Free version.
1104 $config = is_array( $config ) ? $config : array();
1105 $data['strings']['blocklist_monitor'] = $this->format_blocklist_monitor_strings( $config );
1106 } elseif ( 'pwned_passwords' === $key ) {
1107 $data['strings']['pwned_passwords'] = wd_di()->get( Controller_Password_Protection::class )->config_strings( $config );
1108 } elseif ( 'mask_login' === $key ) {
1109 $data['strings']['mask_login'] = wd_di()->get( Controller_Mask_Login::class )->config_strings( $config );
1110 } elseif ( 'security_headers' === $key ) {
1111 $config = is_array( $config ) ? $config : array();
1112 $data['strings']['security_headers'] = wd_di()->get( Security_Headers::class )->config_strings( $config );
1113 } elseif ( 'two_factor' === $key ) {
1114 // Two-Factor is available on both Free and Pro.
1115 $data['strings']['two_factor'] = wd_di()->get( Two_Factor::class )->config_strings( $config );
1116 } elseif ( 'force_strong_password' === $key ) {
1117 $data['strings']['force_strong_password'] = wd_di()->get( Strong_Password_Controller::class )->config_strings( $config );
1118 }
1119 // todo: display session_protection strings.
1120 }
1121
1122 return $data['strings'];
1123 }
1124
1125 /**
1126 * Creates default module strings for the Basic configuration.
1127 *
1128 * @param array $configs The configuration data.
1129 *
1130 * @return array Returns an array of default module strings.
1131 */
1132 public function create_default_module_strings( array $configs ): array {
1133 $strings = array();
1134
1135 foreach ( $configs as $key => $config ) {
1136 if ( 'security_tweaks' === $key ) {
1137 $strings['security_tweaks'] = wd_di()->get( Controller_Security_Tweaks::class )->config_strings( $config );
1138 } elseif ( 'scan' === $key ) {
1139 $strings['scan'] = wd_di()->get( Controller_Scan::class )->config_strings( $config );
1140 } elseif ( 'iplockout' === $key ) {
1141 $strings['iplockout'] = wd_di()->get( Controller_Firewall::class )->config_strings( $config );
1142 } elseif ( 'audit' === $key ) {
1143 $strings['audit'] = wd_di()->get( Audit_Logging::class )->config_strings( $config );
1144 } elseif ( 'two_factor' === $key ) {
1145 $strings['two_factor'][] = esc_html__( 'Inactive', 'defender-security' );
1146 } elseif ( 'mask_login' === $key ) {
1147 $strings['mask_login'] = wd_di()->get( Controller_Mask_Login::class )->config_strings( $config );
1148 } elseif ( 'security_headers' === $key ) {
1149 $strings['security_headers'] = wd_di()->get( Security_Headers::class )->config_strings( $config );
1150 } elseif ( 'blocklist_monitor' === $key ) {
1151 $strings['blocklist_monitor'] = wd_di()->get( Blocklist_Monitor::class )->config_strings( $config );
1152 } elseif ( 'pwned_passwords' === $key ) {
1153 $strings['pwned_passwords'][] = esc_html__( 'Inactive', 'defender-security' );
1154 } elseif ( 'force_strong_password' === $key ) {
1155 $strings['force_strong_password'][] = esc_html__( 'Inactive', 'defender-security' );
1156 }
1157 // todo: display session_protection strings.
1158 }
1159
1160 return $strings;
1161 }
1162
1163 /**
1164 * Retrieves a decoded settings array from the options table.
1165 *
1166 * @param string $key The key under which the settings are stored.
1167 *
1168 * @return array Returns the decoded settings array.
1169 */
1170 private function get_decoded_settings( string $key ): array {
1171 $data = get_site_option( $key );
1172 if ( is_string( $data ) && '' !== trim( $data ) ) {
1173 $decoded_data = json_decode( $data, true );
1174 if ( is_array( $decoded_data ) ) {
1175 return $decoded_data;
1176 }
1177 }
1178
1179 return array();
1180 }
1181
1182 /**
1183 * Get settings of previous version.
1184 *
1185 * @return array
1186 */
1187 private function get_prev_settings(): array {
1188 $arr = array();
1189 if ( $this->has_hub_api_key ) {
1190 $status = (string) wd_di()->get( Blocklist_Monitor::class )->get_status();
1191 $arr['enabled'] = '1' === $status;
1192 $arr['status'] = $status;
1193 } else {
1194 $arr['enabled'] = false;
1195 }
1196
1197 return array(
1198 // Different keys.
1199 'security_tweaks' => $this->get_decoded_settings( 'wd_hardener_settings' ),
1200 'iplockout' => $this->get_decoded_settings( 'wd_lockdown_settings' ),
1201 // Identical keys.
1202 'scan' => $this->get_decoded_settings( 'wd_scan_settings' ),
1203 'audit' => $this->get_decoded_settings( 'wd_audit_settings' ),
1204 'two_factor' => $this->get_decoded_settings( 'wd_2auth_settings' ),
1205 'mask_login' => $this->get_decoded_settings( 'wd_masking_login_settings' ),
1206 'security_headers' => $this->get_decoded_settings( 'wd_security_headers_settings' ),
1207 'settings' => $this->get_decoded_settings( 'wd_main_settings' ),
1208 'blocklist_monitor' => $arr,
1209 );
1210 }
1211
1212 /**
1213 * Converts a module name to a human-readable name.
1214 *
1215 * @param string $module The module name to convert.
1216 *
1217 * @return string Returns the human-readable name of the module.
1218 */
1219 private function module_to_name( string $module ): string {
1220 switch ( $module ) {
1221 case 'security_tweaks':
1222 return esc_html__( 'Hardening', 'defender-security' );
1223 case 'scan':
1224 return esc_html__( 'Issue', 'defender-security' );
1225 case 'audit':
1226 return Model_Audit_Logging::get_module_name();
1227 case 'iplockout':
1228 return esc_html__( 'Firewall', 'defender-security' );
1229 case 'settings':
1230 return esc_html__( 'Settings', 'defender-security' );
1231 case 'two_factor':
1232 return esc_html__( '2FA', 'defender-security' );
1233 case 'mask_login':
1234 return Model_Mask_Login::get_module_name();
1235 case 'security_headers':
1236 return Model_Security_Headers::get_module_name();
1237 case 'blocklist_monitor':
1238 return esc_html__( 'Blocklist Monitor', 'defender-security' );
1239 case 'pwned_passwords':
1240 return Model_Password_Protection::get_module_name();
1241 default:
1242 return '';
1243 }
1244 }
1245
1246 /**
1247 * Retrieves labels for a specific module.
1248 *
1249 * @param string $module The module for which to retrieve labels.
1250 *
1251 * @return array|void Returns an array of labels for the module, or void if the module does not exist.
1252 */
1253 public function model_labels( string $module ) {
1254 switch ( $module ) {
1255 case 'security_tweaks':
1256 return array_merge(
1257 ( new Model_Security_Tweaks() )->labels(),
1258 ( new Tweak_Reminder() )->labels()
1259 );
1260 case 'scan':
1261 return array_merge(
1262 ( new Model_Scan() )->labels(),
1263 ( new Malware_Notification() )->labels(),
1264 ( new Malware_Report() )->labels()
1265 );
1266 case 'audit':
1267 if ( class_exists( Model_Audit_Logging::class ) ) {
1268 return array_merge(
1269 ( new Model_Audit_Logging() )->labels(),
1270 ( new Audit_Report() )->labels()
1271 );
1272 }
1273 break;
1274 case 'iplockout':
1275 return array_merge(
1276 ( new Model_Firewall() )->labels(),
1277 ( new Model_Login_Lockout() )->labels(),
1278 ( new Model_Notfound_Lockout() )->labels(),
1279 ( new Model_Blacklist_Lockout() )->labels(),
1280 ( new Firewall_Notification() )->labels(),
1281 ( new Firewall_Report() )->labels(),
1282 ( new Model_Ua_Lockout() )->labels()
1283 );
1284 case 'settings':
1285 return ( new Model_Main_Setting() )->labels();
1286 case 'two_factor':
1287 return ( new Model_Two_Fa() )->labels();
1288 case 'mask_login':
1289 return ( new Model_Mask_Login() )->labels();
1290 case 'security_headers':
1291 return ( new Model_Security_Headers() )->labels();
1292 case 'blocklist_monitor':
1293 // Separate method is not for this model.
1294 return ( new Blocklist_Monitor() )->labels();
1295 case 'pwned_passwords':
1296 return ( new Model_Password_Protection() )->labels();
1297 default:
1298 break;
1299 }
1300 }
1301
1302 /**
1303 * Parses data for display on the Hub.
1304 *
1305 * @return array Returns an array containing parsed data for the Hub.
1306 */
1307 public function parse_data_for_hub(): array {
1308 $configs = $this->gather_data();
1309 $labels = array();
1310 $strings = array();
1311 foreach ( $configs as $module => $module_data ) {
1312 $model_labels = $this->model_labels( $module );
1313
1314 if ( is_array( $module_data ) && is_array( $model_labels ) ) {
1315 $labels[ $module ]['name'] = $this->module_to_name( $module );
1316 foreach ( $module_data as $key => $value ) {
1317 // Todo: update logic to import/export whitelisted/blocklisted countries via maxmind_license_key.
1318 if ( in_array( $key, array( 'geoIP_db', 'geodb_path' ), true ) ) {
1319 continue;
1320 }
1321
1322 if ( array_key_exists( $key, $model_labels ) ) {
1323 $labels[ $module ]['value'][ $key ] = array(
1324 'name' => $model_labels[ $key ],
1325 'value' => $value,
1326 );
1327 }
1328 }
1329
1330 $controller = $this->module_to_controller( $module, false );
1331 if ( ! is_object( $controller ) ) {
1332 // In free, when audit not present.
1333 $strings[ $module ] = sprintf(
1334 /* translators: %s: Html for Pro-tag. */
1335 esc_html__( 'Inactive %s', 'defender-security' ),
1336 '<span class="sui-tag sui-tag-pro">Pro</span>'
1337 );
1338 continue;
1339 }
1340 $strings[ $module ] = $controller->export_strings();
1341 }
1342 }
1343
1344 return array(
1345 'configs' => $configs,
1346 'labels' => $labels,
1347 'strings' => $strings,
1348 );
1349 }
1350
1351 /**
1352 * Format strings of Blocklist Monitor config.
1353 *
1354 * @param array $config Saved config.
1355 *
1356 * @return array
1357 */
1358 private function format_blocklist_monitor_strings( array $config ): array {
1359 if ( $this->has_hub_api_key ) {
1360 $monitor = isset( $config['status'] ) && '1' === (string) $config['status']
1361 ? array( esc_html__( 'Blocklist Monitor active', 'defender-security' ) )
1362 : array( esc_html__( 'Blocklist Monitor inactive', 'defender-security' ) );
1363 } else {
1364 $monitor = array( esc_html__( 'Blocklist Monitor inactive', 'defender-security' ) );
1365 }
1366
1367 return $monitor;
1368 }
1369
1370 /**
1371 * Prepares labels from configuration data.
1372 *
1373 * @param array $configs The configuration data.
1374 *
1375 * @return array Returns an array of prepared labels.
1376 */
1377 public function prepare_config_labels( array $configs ): array {
1378 $labels = array();
1379
1380 foreach ( $configs as $module => $module_data ) {
1381 $model_labels = $this->model_labels( $module );
1382
1383 if ( is_array( $module_data ) && is_array( $model_labels ) ) {
1384 $labels[ $module ]['name'] = $this->module_to_name( $module );
1385 foreach ( $module_data as $key => $value ) {
1386 // Todo: update logic to import/export whitelisted/blocklisted countries via maxmind_license_key.
1387 if ( in_array( $key, array( 'geoIP_db', 'geodb_path' ), true ) ) {
1388 continue;
1389 }
1390
1391 if ( array_key_exists( $key, $model_labels ) ) {
1392 $labels[ $module ]['value'][ $key ] = array(
1393 'name' => $model_labels[ $key ],
1394 'value' => $value,
1395 );
1396 }
1397 }
1398 }
1399 }
1400
1401 return $labels;
1402 }
1403
1404 /**
1405 * User enumeration options enabled list.
1406 *
1407 * @return array Return array of enabled user enumeration options.
1408 */
1409 private function get_enabled_user_enums() {
1410 $prevent_enum_users = new Prevent_Enum_Users();
1411
1412 return $prevent_enum_users->get_enabled_user_enums();
1413 }
1414
1415 /**
1416 * Security key all option.
1417 *
1418 * @return mixed All value of the "security key" feature of security tweak.
1419 */
1420 private function get_security_key_all_options() {
1421 $security_key = new Security_Key();
1422
1423 return $security_key->get_all_option();
1424 }
1425 }
1426