PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.3.0
Defender Security – Malware Scanner, Login Security & Firewall v6.3.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.3.0, at src/component/class-backup-settings.php

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