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

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

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