<?php
/**
 * Handles backup and restoration of settings.
 *
 * @package WP_Defender\Component
 */

namespace WP_Defender\Component;

use Countable;
use WP_Defender\Component;
use WP_Defender\Traits\IP;
use WP_Defender\Behavior\WPMUDEV;
use WP_Defender\Controller\Blacklist;
use WP_Defender\Controller\Global_Ip;
use WP_Defender\Controller\Two_Factor;
use WP_Defender\Controller\Nf_Lockout;
use WP_Defender\Controller\Main_Setting;
use WP_Defender\Controller\Audit_Logging;
use WP_Defender\Controller\Security_Headers;
use WP_Defender\Controller\Blocklist_Monitor;
use WP_Defender\Model\Notification\Audit_Report;
use WP_Defender\Model\Setting\Global_Ip_Lockout;
use WP_Defender\Model\Setting\Scan as Model_Scan;
use WP_Defender\Controller\Scan as Controller_Scan;
use WP_Defender\Model\Notification\Malware_Report;
use WP_Defender\Model\Notification\Tweak_Reminder;
use WP_Defender\Model\Notification\Firewall_Report;
use WP_Defender\Component\Config\Config_Hub_Helper;
use WP_Defender\Model\Setting\Two_Fa as Model_Two_Fa;
use WP_Defender\Component\Security_Tweaks\Security_Key;
use WP_Defender\Model\Setting\Antibot_Global_Firewall_Setting;
use WP_Defender\Model\Notification\Malware_Notification;
use WP_Defender\Model\Setting\Firewall as Model_Firewall;
use WP_Defender\Model\Notification\Firewall_Notification;
use WP_Defender\Controller\Firewall as Controller_Firewall;
use WP_Defender\Model\Setting\Mask_Login as Model_Mask_Login;
use WP_Defender\Component\Security_Tweaks\Prevent_Enum_Users;
use WP_Defender\Controller\Mask_Login as Controller_Mask_Login;
use WP_Defender\Controller\UA_Lockout as Controller_Ua_Lockout;
use WP_Defender\Model\Setting\Main_Setting as Model_Main_Setting;
use WP_Defender\Model\Setting\Audit_Logging as Model_Audit_Logging;
use WP_Defender\Model\Setting\Login_Lockout as Model_Login_Lockout;
use WP_Defender\Model\Setting\User_Agent_Lockout as Model_Ua_Lockout;
use WP_Defender\Model\Setting\Strong_Password as Strong_Password_Model;
use WP_Defender\Model\Setting\Security_Tweaks as Model_Security_Tweaks;
use WP_Defender\Controller\Strong_Password as Strong_Password_Controller;
use WP_Defender\Controller\Security_Tweaks as Controller_Security_Tweaks;
use WP_Defender\Model\Setting\Notfound_Lockout as Model_Notfound_Lockout;
use WP_Defender\Model\Setting\Security_Headers as Model_Security_Headers;
use WP_Defender\Model\Setting\Blacklist_Lockout as Model_Blacklist_Lockout;
use WP_Defender\Model\Setting\Session_Protection as Session_Protection_Model;
use WP_Defender\Controller\Session_Protection as Session_Protection_Controller;
use WP_Defender\Model\Setting\Password_Protection as Model_Password_Protection;
use WP_Defender\Controller\Password_Protection as Controller_Password_Protection;

/**
 * Handles backup and restoration of settings.
 */
class Backup_Settings extends Component {

	use IP;

	public const KEY = 'defender_last_settings', INDEXER = 'defender_config_indexer';

	/**
	 * Indicates whether the current installation is a pro version.
	 *
	 * @var bool
	 */
	private $is_pro;

	/**
	 * Constructor for the Backup_Settings class.
	 * It initializes the class and sets whether the current installation is a pro version.
	 */
	public function __construct() {
		$this->is_pro = ( new WPMUDEV() )->is_pro();
	}

	/**
	 * Changes the format of subscribers in a notification object to an array format.
	 *
	 * @param  object $notification_object  The notification object to format.
	 *
	 * @return array Returns an array of subscribers formatted.
	 */
	public function change_subscriber_format( $notification_object ): array {
		if (
			! is_object( $notification_object )
			&& ! is_array( $notification_object->in_house_recipients )
			&& ! is_array( $notification_object->out_house_recipients )
		) {
			return array();
		}
		if ( array() === $notification_object->in_house_recipients && array() === $notification_object->out_house_recipients ) {
			return array();
		}

		$subscribers = array();
		if ( array() !== $notification_object->in_house_recipients ) {
			$subscribers['in_house_recipients'] = $notification_object->in_house_recipients;
		}
		if ( array() !== $notification_object->out_house_recipients ) {
			$subscribers['out_house_recipients'] = $notification_object->out_house_recipients;
		}

		return $subscribers;
	}

	/**
	 * Gather settings from all modules.
	 *
	 * @return array
	 */
	public function gather_data(): array {
		$audit       = array();
		$tweak_class = wd_di()->get( Controller_Security_Tweaks::class );
		$tweak_class->refresh_tweaks_status();
		$settings           = new Model_Security_Tweaks();
		$tweak_notification = new Tweak_Reminder();
		$security_tweaks    = array(
			'notification_repeat' => $tweak_notification->configs['reminder'],
			'subscribers'         => $this->change_subscriber_format( $tweak_notification ),
			'notification'        => $tweak_notification->status,
			'data'                => $settings->data,
			'fixed'               => $settings->fixed,
			'issues'              => $settings->issues,
			'ignore'              => $settings->ignore,
			'automate'            => $settings->automate,
			'enabled_user_enums'  => $this->get_enabled_user_enums(),
			'security_key'        => $this->get_security_key_all_options(),
		);
		$settings           = new Model_Scan();
		$scan_report        = new Malware_Report();
		$scan_notification  = new Malware_Notification();
		$scan               = array(
			'integrity_check'               => $settings->integrity_check,
			'check_core'                    => $settings->check_core,
			'check_plugins'                 => $settings->check_plugins,
			'check_known_vuln'              => $settings->check_known_vuln,
			'scan_malware'                  => $settings->scan_malware,
			'filesize'                      => $settings->filesize,
			// @since 2.7.0 changes for Scheduled options.
			'scheduled_scanning'            => $settings->scheduled_scanning,
			'day'                           => $settings->day,
			'day_n'                         => $settings->day_n,
			'time'                          => $settings->time,
			'frequency'                     => $settings->frequency,
			'report'                        => $scan_report->status,
			'always_send'                   => $scan_report->configs['always_send'],
			'report_subscribers'            => $this->change_subscriber_format( $scan_report ),
			// @since 2.7.0 for backward compatibility. We can remove it in the next version.
			'dry_run'                       => false,
			'notification'                  => $scan_notification->status,
			'always_send_notification'      => $scan_notification->configs['always_send'],
			'error_send'                    => $scan_notification->configs['error_send'],
			'notification_subscribers'      => $this->change_subscriber_format( $scan_notification ),
			'email_subject_issue_not_found' => $scan_notification->configs['template']['not_found']['subject'],
			'email_subject_issue_found'     => $scan_notification->configs['template']['found']['subject'],
			'email_subject_error'           => $scan_notification->configs['template']['error']['subject'],
			'email_content_issue_not_found' => $scan_notification->configs['template']['not_found']['body'],
			'email_content_issue_found'     => $scan_notification->configs['template']['found']['body'],
			'email_content_error'           => $scan_notification->configs['template']['error']['body'],
		);
		if ( class_exists( Model_Audit_Logging::class ) ) {
			$settings     = new Model_Audit_Logging();
			$audit_report = new Audit_Report();
			$audit        = array(
				'enabled'      => $settings->is_active(),
				'report'       => $audit_report->status,
				'subscribers'  => $this->change_subscriber_format( $audit_report ),
				'frequency'    => $audit_report->frequency,
				'day'          => $audit_report->day,
				'day_n'        => $audit_report->day_n,
				'time'         => $audit_report->time,
				// @since 2.7.0 We can remove it in the next version.
				'dry_run'      => false,
				'storage_days' => $settings->storage_days,
			);
			if ( ! $this->is_pro ) {
				$audit['enabled'] = false;
			}
		} else {
			$audit['enabled'] = false;
		}
		$settings_firewall    = new Model_Firewall();
		$settings_ll          = new Model_Login_Lockout();
		$settings_nl          = new Model_Notfound_Lockout();
		$settings_bl          = new Model_Blacklist_Lockout();
		$lockout_notification = new Firewall_Notification();
		$lockout_report       = new Firewall_Report();
		$ua_banning_model     = new Model_Ua_Lockout();
		$settings_gi          = wd_di()->get( Global_Ip_Lockout::class );
		$settings_antibot     = wd_di()->get( Antibot_Global_Firewall_Setting::class );
		$iplockout            = array(
			'login_protection'                       => $settings_ll->enabled,
			'login_protection_login_attempt'         => $settings_ll->attempt,
			'login_protection_lockout_timeframe'     => $settings_ll->timeframe,
			'login_protection_lockout_ban'           => $settings_ll->lockout_type,
			'login_protection_lockout_duration'      => $settings_ll->duration,
			'login_protection_lockout_duration_unit' => $settings_ll->duration_unit,
			'login_protection_lockout_message'       => $settings_ll->lockout_message,
			'username_blacklist'                     => $settings_ll->username_blacklist,
			'detect_404'                             => $settings_nl->enabled,
			'detect_404_threshold'                   => $settings_nl->attempt,
			'detect_404_timeframe'                   => $settings_nl->timeframe,
			'detect_404_lockout_ban'                 => $settings_nl->lockout_type,
			'detect_404_lockout_duration'            => $settings_nl->duration,
			'detect_404_lockout_duration_unit'       => $settings_nl->duration_unit,
			'detect_404_lockout_message'             => $settings_nl->lockout_message,
			'detect_404_blacklist'                   => $settings_nl->blacklist,
			'detect_404_whitelist'                   => $settings_nl->whitelist,
			'detect_404_logged'                      => $settings_nl->detect_logged,
			'ip_blacklist'                           => $settings_bl->ip_blacklist,
			'ip_whitelist'                           => $settings_bl->ip_whitelist,
			'country_blacklist'                      => $settings_bl->country_blacklist,
			'country_whitelist'                      => $settings_bl->country_whitelist,
			'ip_lockout_message'                     => $settings_bl->ip_lockout_message,
			'login_lockout_notification'             => $lockout_notification->configs['login_lockout'],
			'ip_lockout_notification'                => $lockout_notification->configs['nf_lockout'],
			'notification'                           => $lockout_notification->status,
			'notification_subscribers'               => $this->change_subscriber_format( $lockout_notification ),
			'cooldown_enabled'                       => $lockout_notification->configs['limit'],
			'cooldown_number_lockout'                => $lockout_notification->configs['threshold'],
			'cooldown_period'                        => $lockout_notification->configs['cool_off'],
			'report'                                 => $lockout_report->status,
			'report_subscribers'                     => $this->change_subscriber_format( $lockout_report ),
			'report_frequency'                       => $lockout_report->frequency,
			'day'                                    => $lockout_report->day,
			'day_n'                                  => $lockout_report->day_n,
			'report_time'                            => $lockout_report->time,
			// @since 2.7.0 We can remove it in the next version.
			'dry_run'                                => false,
			'storage_days'                           => $settings_firewall->storage_days,
			'geoIP_db'                               => $settings_bl->geodb_path,
			'maxmind_license_key'                    => $settings_bl->maxmind_license_key,
			'ip_blocklist_cleanup_interval'          => $settings_firewall->ip_blocklist_cleanup_interval,
			'ua_banning_enabled'                     => $ua_banning_model->enabled,
			'ua_banning_message'                     => $ua_banning_model->message,
			'ua_banning_blacklist'                   => $ua_banning_model->blacklist,
			'ua_banning_whitelist'                   => $ua_banning_model->whitelist,
			'ua_banning_empty_headers'               => $ua_banning_model->empty_headers,
			'global_ip_list'                         => $settings_gi->enabled,
			'global_ip_list_blocklist_autosync'      => $settings_gi->blocklist_autosync,
			'antibot'                                => $settings_antibot->enabled,
		);
		$settings_two_fa      = new Model_Two_Fa();
		$settings_mask_login  = new Model_Mask_Login();
		$advanced_tools       = array(
			'two_factor' => $settings_two_fa->export(),
			'mask_login' => $settings_mask_login->export(),
		);
		$settings_main        = new Model_Main_Setting();
		$main_settings        = $settings_main->export();
		$settings_sec_headers = new Model_Security_Headers();
		$security_headers     = $settings_sec_headers->export();
		$ret                  = array(
			'scan'             => $scan,
			'iplockout'        => $iplockout,
			'two_factor'       => $advanced_tools['two_factor'],
			'mask_login'       => $advanced_tools['mask_login'],
			'settings'         => $main_settings,
			'security_headers' => $security_headers,
		);
		if ( isset( $audit ) ) {
			$ret['audit'] = $audit;
		}
		// For Blocklist_Monitor.
		if ( $this->is_pro ) {
			$blocklist_monitor_class             = wd_di()->get( Blocklist_Monitor::class );
			$status                              = (string) $blocklist_monitor_class->get_status();
			$ret['blocklist_monitor']['enabled'] = '1' === $status;
			$ret['blocklist_monitor']['status']  = $status;
		} else {
			$ret['blocklist_monitor']['enabled'] = false;
		}
		// For Pwned passwords.
		$pwned_password_model                     = wd_di()->get( Model_Password_Protection::class );
		$ret['pwned_passwords']['enabled']        = $pwned_password_model->is_active();
		$ret['pwned_passwords']['user_roles']     = $pwned_password_model->user_roles;
		$ret['pwned_passwords']['custom_message'] = $pwned_password_model->pwned_actions['force_change_message'];
		// For Strong Passwords.
		$strong_password_model                      = wd_di()->get( Strong_Password_Model::class );
		$ret['force_strong_password']['enabled']    = $strong_password_model->is_active();
		$ret['force_strong_password']['user_roles'] = $strong_password_model->user_roles;
		$ret['force_strong_password']['message']    = $strong_password_model->get_message();
		// For Session Protection.
		if ( $this->is_pro ) {
			$session_protection_model                     = wd_di()->get( Session_Protection_Model::class );
			$ret['session_protection']['enabled']         = $session_protection_model->enabled;
			$ret['session_protection']['user_roles']      = $session_protection_model->user_roles;
			$ret['session_protection']['lock_properties'] = $session_protection_model->lock_properties;
			$ret['session_protection']['idle_timeout']    = $session_protection_model->idle_timeout;
			$ret['session_protection']['login_duration']  = $session_protection_model->login_duration;
		} else {
			$ret['session_protection']['enabled'] = false;
		}
		// It's better to add Tweaks as the last key to eliminate unexpected behavior with possible user logout.
		$ret['security_tweaks'] = $security_tweaks;

		return $ret;
	}

	/**
	 * Retrieves all configurations from the options table.
	 *
	 * @return array Returns an array of all configurations.
	 */
	public function get_configs(): array {
		$keys    = get_site_option( self::INDEXER, false );
		$results = array();
		if ( ! is_array( $keys ) || array() === $keys ) {
			return $results;
		}

		foreach ( $keys as $key ) {
			$config = get_site_option( $key );

			if ( false === $config ) {
				$this->remove_index( $key );
			} else {
				$results[ $key ] = $config;
			}
		}

		return $results;
	}

	/**
	 * Sets a specific configuration as active.
	 *
	 * @param  string $key  The key of the configuration to make active.
	 */
	public function make_config_active( string $key ): void {
		$configs = $this->get_configs();
		foreach ( $configs as $k => $config ) {
			if ( $k === $key ) {
				$config['is_active'] = true;
			} else {
				$config['is_active'] = false;
			}
			update_site_option( $k, $config );
		}
	}

	/**
	 * Clear keys if the last config was deleted.
	 *
	 * @return void
	 */
	public function clear_keys(): void {
		$keys = get_site_option( self::INDEXER, array() );
		if ( array() === $keys ) {
			delete_site_option( self::INDEXER );
			delete_site_option( self::KEY );
		}
	}

	/**
	 * Clears all configurations from the options table.
	 */
	public function clear_configs(): void {
		$keys = get_site_option( self::INDEXER, false );
		if ( is_array( $keys ) && array() !== $keys ) {
			foreach ( $keys as $key ) {
				delete_site_option( $key );
			}
		}
		delete_site_option( self::INDEXER );
		delete_site_option( self::KEY );
		delete_site_transient( Config_Hub_Helper::CONFIGS_TRANSIENT_KEY );
	}

	/**
	 * Do the deletion only if there are no indexed configs, so as not to delete the displayed configs.
	 */
	protected function remove_unindexed_configs() {
		global $wpdb;

		$result = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
			$wpdb->prepare(
				"SELECT option_name FROM {$wpdb->prefix}options WHERE option_name LIKE %s",
				'%wp_defender_config_%'
			),
			ARRAY_A
		);
		if ( array() !== $result ) {
			foreach ( $result as $arr ) {
				delete_site_option( $arr['option_name'] );
			}
		}
	}

	/**
	 * Create a default config.
	 *
	 * @return void
	 */
	public function maybe_create_default_config(): void {
		$keys = get_site_option( self::INDEXER, array() );
		if ( array() === $keys ) {
			$this->remove_unindexed_configs();
			$key    = 'wp_defender_config_default' . time();
			$config = get_site_option( $key );
			if ( ! is_array( $config ) || array() === $config ) {
				$this->create_basic_config( $key );
			}
		}
	}

	/**
	 * Creates a basic configuration and saves it to the options table.
	 *
	 * @param  string $key  The key under which to save the configuration.
	 *
	 * @return void
	 * @since 2.6.5 Reduce differences between Basic and Default security configs.
	 */
	private function create_basic_config( string $key ): void {
		$configs = array();
		// @since 2.6.5 Clear recipients.
		$default_recipients = array();
		// Init Tweaks class and refresh status.
		$tweak_class = wd_di()->get( Controller_Security_Tweaks::class );
		$tweak_class->refresh_tweaks_status();
		// Get user role keys.
		$user_roles = array_keys( get_editable_roles() );
		// Default values.
		$default_scan_notification_values   = ( new Malware_Notification() )->get_default_values();
		$default_login_lockout_values       = ( new Model_Login_Lockout() )->get_default_values();
		$default_404_lockout_values         = ( new Model_Notfound_Lockout() )->get_default_values();
		$default_ip_lockout_values          = ( new Model_Blacklist_Lockout() )->get_default_values();
		$default_ua_lockout_values          = ( new Model_Ua_Lockout() )->get_default_values();
		$default_password_protection_values = ( new Model_Password_Protection() )->get_default_values();
		$default_2fa_values                 = ( new Model_Two_Fa() )->get_default_values();
		// Total data.
		$data = array(
			'scan'                  => array(
				'integrity_check'               => true,
				'check_core'                    => true,
				'check_plugins'                 => true,
				'check_known_vuln'              => true,
				'scan_malware'                  => true,
				'filesize'                      => 3,
				'report'                        => 'enabled',
				'always_send'                   => false,
				'report_subscribers'            => $default_recipients,
				'day'                           => 'sunday',
				'day_n'                         => 1,
				'time'                          => '4:00',
				'frequency'                     => 'weekly',
				// @since 2.7.0 We can remove it in the next version.
				'dry_run'                       => false,
				'notification'                  => 'enabled',
				'always_send_notification'      => false,
				'error_send'                    => false,
				'notification_subscribers'      => $default_recipients,
				'email_subject_issue_found'     => $default_scan_notification_values['subject_issue_found'],
				'email_subject_issue_not_found' => $default_scan_notification_values['subject_issue_not_found'],
				'email_subject_error'           => $default_scan_notification_values['subject_error'],
				'email_content_issue_found'     => $default_scan_notification_values['content_issue_found'],
				'email_content_issue_not_found' => $default_scan_notification_values['content_issue_not_found'],
				'email_content_error'           => $default_scan_notification_values['content_error'],
				// @since 2.7.0 move Scheduled options from Malware Scanning - Reporting to Malware settings.
				// Values for frequency, day and time are above.
				'scheduled_scanning'            => true,
			),
			'iplockout'             => array(
				'login_protection'                       => true,
				'login_protection_login_attempt'         => '5',
				'login_protection_lockout_timeframe'     => '300',
				'login_protection_lockout_ban'           => 'timeframe',
				'login_protection_lockout_duration'      => '4',
				'login_protection_lockout_duration_unit' => 'hours',
				'login_protection_lockout_message'       => $default_login_lockout_values['message'],
				// @since 2.7.0 New default blocklisted usernames.
				'username_blacklist'                     => "adm\nadmin\nadmin1\nhostname\nmanager\nqwerty\nroot\nsupport\nsysadmin\ntest\nuser\nadministrator",
				'detect_404'                             => true,
				'detect_404_threshold'                   => '20',
				'detect_404_timeframe'                   => '300',
				'detect_404_lockout_ban'                 => 'timeframe',
				'detect_404_lockout_duration'            => '4',
				'detect_404_lockout_duration_unit'       => 'hours',
				'detect_404_lockout_message'             => $default_404_lockout_values['message'],
				'detect_404_blacklist'                   => '',
				// @since 2.7.0 New default whitelisted extensions.
				'detect_404_whitelist'                   => ".css\n.js\n.jpg\n.png\n.gif\n.map",
				'detect_404_logged'                      => true,
				'ip_blacklist'                           => '',
				// @since 2.6.5 Clear the current user IP.
				'ip_whitelist'                           => '',
				'country_blacklist'                      => '',
				'country_whitelist'                      => '',
				'ip_lockout_message'                     => $default_ip_lockout_values['message'],
				'login_lockout_notification'             => true,
				'ip_lockout_notification'                => true,
				'notification'                           => 'enabled',
				'notification_subscribers'               => $default_recipients,
				'cooldown_enabled'                       => false,
				'cooldown_number_lockout'                => '3',
				'cooldown_period'                        => '24',
				'report'                                 => 'enabled',
				'report_subscribers'                     => $default_recipients,
				'report_frequency'                       => 'weekly',
				'day'                                    => 'sunday',
				'day_n'                                  => 1,
				'report_time'                            => '4:00',
				// @since 2.7.0 We can remove it in the next version.
				'dry_run'                                => false,
				'storage_days'                           => '180',
				'geoIP_db'                               => '',
				'ip_blocklist_cleanup_interval'          => 'never',
				'ua_banning_enabled'                     => false,
				'ua_banning_message'                     => $default_ua_lockout_values['message'],
				'ua_banning_blacklist'                   => $default_ua_lockout_values['blacklist'],
				'ua_banning_whitelist'                   => $default_ua_lockout_values['whitelist'],
				'ua_banning_empty_headers'               => false,
				// @since 2.7.1 New key of Blacklist_Lockout.
				'maxmind_license_key'                    => '',
				// @since 3.4.0 New Global IP list.
				'global_ip_list'                         => false,
				'global_ip_list_blocklist_autosync'      => false,
			),
			'two_factor'            => array(
				// @since 2.6.5 Disabled module.
				'enabled'             => false,
				'lost_phone'          => true,
				'force_auth'          => false,
				'force_auth_mess'     => $default_2fa_values['message'],
				'user_roles'          => $user_roles,
				'force_auth_roles'    => array(),
				'custom_graphic'      => false,
				'custom_graphic_type' => Model_Two_Fa::CUSTOM_GRAPHIC_TYPE_UPLOAD,
				'custom_graphic_url'  => '',
				'custom_graphic_link' => '',
				'email_subject'       => $default_2fa_values['email_subject'],
				'email_sender'        => $default_2fa_values['email_sender'],
				'email_body'          => $default_2fa_values['email_body'],
				'app_title'           => $default_2fa_values['app_title'],
			),
			'mask_login'            => array(
				'enabled'                  => false,
				'mask_url'                 => '',
				'redirect_traffic'         => 'off',
				'redirect_traffic_url'     => '',
				'redirect_traffic_page_id' => 0,
			),
			'security_headers'      => array(
				'sh_xframe'                    => true,
				'sh_xframe_mode'               => 'sameorigin',
				'sh_xframe_urls'               => '',
				'sh_xss_protection'            => true,
				'sh_xss_protection_mode'       => 'sanitize',
				'sh_content_type_options'      => true,
				'sh_content_type_options_mode' => 'nosniff',
				'sh_strict_transport'          => true,
				'hsts_preload'                 => false,
				'include_subdomain'            => false,
				'hsts_cache_duration'          => '30 days',
				'sh_referrer_policy'           => true,
				'sh_referrer_policy_mode'      => 'origin-when-cross-origin',
				'sh_feature_policy'            => true,
				'sh_feature_policy_mode'       => 'self',
				'sh_feature_policy_urls'       => '',
			),
			'settings'              => array(
				'uninstall_data'     => 'keep',
				'uninstall_settings' => 'preserve',
				// @since 2.7.0 Empty.
				'translate'          => '',
				'usage_tracking'     => false,
				'high_contrast_mode' => false,
			),
			'pwned_passwords'       => array(
				'enabled'        => false,
				'user_roles'     => $user_roles,
				'custom_message' => $default_password_protection_values['message'],
			),
			'force_strong_password' => array(
				'enabled'    => false,
				'user_roles' => $user_roles,
				'message'    => ( new Strong_Password_Model() )->get_message(),
			),
		);
		// Pro properties.
		if ( $this->is_pro ) {
			$data['audit']              = array(
				'enabled'      => true,
				'report'       => 'enabled',
				'subscribers'  => $default_recipients,
				'frequency'    => 'weekly',
				'day'          => 'sunday',
				'day_n'        => 1,
				'time'         => '4:00',
				// @since 2.7.0 We can remove it in the next version.
				'dry_run'      => false,
				'storage_days' => '6 months',
			);
			$data['blocklist_monitor']  = array(
				// @since 2.7.0 Enable.
				'enabled' => true,
				'status'  => '1',
			);
			$data['session_protection'] = array(
				'enabled'         => false,
				'idle_timeout'    => 1,
				'login_duration'  => ( new Session_Protection_Model() )->get_default_duration(),
				'user_roles'      => $user_roles,
				'lock_properties' => array(),
			);
		} else {
			$data['audit']['enabled']             = false;
			$data['blocklist_monitor']['enabled'] = false;
		}
		// It's better to add Tweaks as the last key to eliminate unexpected behavior with possible user logout.
		$data['security_tweaks'] = array(
			'notification_repeat' => 'weekly',
			'subscribers'         => $default_recipients,
			'notification'        => 'enabled',
			'automate'            => true,
			'data'                => array(),
			// @since 2.7.0 Specific values for 4 fixed, 0 ignored and 8 actioned tweaks. 12 tweaks in total.
			'fixed'               => array(
				'disable-xml-rpc',
				'login-duration',
				'disable-trackback',
				'prevent-enum-users',
			),
			'issues'              => array(
				'php-version',
				'wp-version',
				'prevent-php-executed',
				'protect-information',
				'replace-admin-username',
				'security-key',
				'disable-file-editor',
				'hide-error',
			),
			'ignore'              => array(),
		);

		$configs['configs']      = $data;
		$configs['strings']      = $this->create_default_module_strings( $data, $this->is_pro );
		$configs['name']         = esc_html__( 'Basic Config', 'defender-security' );
		$configs['description']  = esc_html__( 'Recommended default protection for every site', 'defender-security' );
		$configs['immortal']     = true;
		$configs['is_removable'] = true;
		update_site_option( $key, $configs );
		$this->index_key( $key );
	}

	/**
	 * Adds a configuration key to the indexer in the options table.
	 *
	 * @param  string $key  The configuration key to index.
	 */
	public function index_key( string $key ): void {
		$keys = get_site_option( self::INDEXER, array() );
		// Check for uniqueness.
		if ( is_array( $keys ) ) {
			$keys[ $key ] = $key;
			$keys         = array_unique( $keys );
			update_site_option( self::INDEXER, $keys );
		} elseif ( '' === $keys ) {
			// The first config.
			update_site_option( self::INDEXER, array( $key => $key ) );
		}
	}

	/**
	 * Removes a configuration key from the indexer in the options table.
	 *
	 * @param  string $key  The configuration key to remove from the index.
	 */
	public function remove_index( string $key ): void {
		$keys = get_site_option( self::INDEXER, false );
		unset( $keys[ array_search( $key, $keys, true ) ] );
		update_site_option( self::INDEXER, $keys );
	}

	/**
	 * Backup the previous data before we process new version.
	 *
	 * @return array
	 */
	public function backup_data(): array {
		$data       = $this->get_prev_settings();
		$old_backup = get_site_option( self::KEY );
		if ( ! is_array( $old_backup ) ) {
			$old_backup = array();
		}
		if ( count( $old_backup ) > 20 ) {
			// Remove the oldest key.
			$old_backup = array_shift( $old_backup );
		}
		$version                               = get_site_option( 'wd_db_version' );
		$old_backup[ $version . '_' . time() ] = $data;
		update_site_option( self::KEY, $old_backup );

		return $data;
	}

	/**
	 * Restores data from a backup.
	 *
	 * @param  array  $data  The data to restore.
	 * @param  string $request_reason  The reason for the data restoration.
	 *
	 * @return bool|string Returns true if re-authentication is needed, otherwise false.
	 */
	public function restore_data( array $data, string $request_reason = 'plugin' ) {
		$need_reauth = false;
		foreach ( $data as $module => $module_data ) {
			if ( ! is_array( $module_data ) ) {
				continue;
			}

			$controller = $this->module_to_controller( $module, true );
			// Return array of objects if the module is IP Lockout.
			if ( is_object( $controller ) || is_array( $controller ) ) {
				foreach ( $module_data as &$value ) {
					if ( isset( $value ) && ! is_array( $value ) && ! filter_var( $value, FILTER_VALIDATE_BOOLEAN ) ) {
						$value = str_replace( '{nl}', PHP_EOL, $value );
					}
				}

				/**
				 * Import the data and perform separate actions for each of the main modules:
				 * Scan, Firewall, Audit, Block list Monitor, Pwned Passwords,
				 * 2FA, Mask Login, Security Headers, Main settings, Tweaks.
				 */
				if ( 'scan' === $module ) {
					// Import.
					$controller->import_data( $module_data );

					$scan_notification = new Malware_Notification();
					$scan_report       = new Malware_Report();
					if ( array() !== $module_data ) {
						$scan_settings = new Model_Scan();
						// For Scan notification.
						if ( isset( $module_data['notification'] ) ) {
							if ( $scan_notification->status !== $module_data['notification'] ) {
								$scan_notification->status = $module_data['notification'];
							}
							if ( isset( $module_data['always_send_notification'] ) ) {
								$scan_notification->configs['always_send'] = $module_data['always_send_notification'];
							}
							if ( isset( $module_data['error_send'] ) ) {
								$scan_notification->configs['error_send'] = $module_data['error_send'];
							}
							if ( isset( $module_data['email_subject_issue_found'] ) && '' !== $module_data['email_subject_issue_found'] ) {
								$scan_notification->configs['template']['found']['subject'] = $module_data['email_subject_issue_found'];
							}
							if ( isset( $module_data['email_subject_issue_not_found'] ) && '' !== $module_data['email_subject_issue_not_found'] ) {
								$scan_notification->configs['template']['not_found']['subject'] = $module_data['email_subject_issue_not_found'];
							}
							if ( isset( $module_data['email_subject_error'] ) && '' !== $module_data['email_subject_error'] ) {
								$scan_notification->configs['template']['error']['subject'] = $module_data['email_subject_error'];
							}
							if ( isset( $module_data['email_content_issue_found'] ) && '' !== $module_data['email_content_issue_found'] ) {
								$scan_notification->configs['template']['found']['body'] = $module_data['email_content_issue_found'];
							}
							if ( isset( $module_data['email_content_issue_not_found'] ) && '' !== $module_data['email_content_issue_not_found'] ) {
								$scan_notification->configs['template']['not_found']['body'] = $module_data['email_content_issue_not_found'];
							}
							if ( isset( $module_data['email_content_error'] ) && '' !== $module_data['email_content_error'] ) {
								$scan_notification->configs['template']['error']['body'] = $module_data['email_content_error'];
							}
							if (
								isset( $module_data['notification_subscribers'] )
								&& is_array( $module_data['notification_subscribers'] )
								&& array() !== $module_data['notification_subscribers']
							) {
								// Reset all recipients before.
								$scan_notification->in_house_recipients  = array();
								$scan_notification->out_house_recipients = array();
								foreach ( $module_data['notification_subscribers'] as $key => $subscribers ) {
									$scan_notification->$key = $subscribers;
								}
							}
							$scan_notification->save();
						}
						// For Scan report.
						if (
							$this->is_pro
							&& isset( $module_data['report'] )
							&& $scan_report->status !== $module_data['report']
						) {
							$scan_report->status = $module_data['report'];
						}
						if ( isset( $module_data['always_send'] ) ) {
							$scan_report->configs['always_send'] = $module_data['always_send'];
						}
						if ( isset( $module_data['report_subscribers'] ) && is_array( $module_data['report_subscribers'] ) && array() !== $module_data['report_subscribers'] ) {
							// Reset all recipients before.
							$scan_report->in_house_recipients  = array();
							$scan_report->out_house_recipients = array();
							foreach ( $module_data['report_subscribers'] as $key => $subscribers ) {
								$scan_report->$key = $subscribers;
							}
						}
						// @since 2.7.0 Remove 'dry_run'-restoring.
						// @since 2.7.0 scheduled values. Step#1 if 'report'-key exists.
						if ( $this->is_pro && isset( $module_data['report'] ) ) {
							$scan_settings->scheduled_scanning = 'enabled' === $module_data['report'];
						}
						// Step#2 if 'scheduled_scanning'-key exists.
						if ( isset( $module_data['scheduled_scanning'] ) ) {
							$scan_settings->scheduled_scanning = $module_data['scheduled_scanning'];
						}
						// We update the values in Model_Scan and Malware_Report so that the values are synchronized in both models.
						if ( isset( $module_data['frequency'] ) ) {
							$scan_settings->frequency = $module_data['frequency'];
							$scan_report->frequency   = $module_data['frequency'];
						}
						if ( isset( $module_data['day'] ) ) {
							$scan_settings->day = $module_data['day'];
							$scan_report->day   = $module_data['day'];
						}
						if ( isset( $module_data['day_n'] ) ) {
							$scan_settings->day_n = (int) $module_data['day_n'];
							$scan_report->day_n   = (int) $module_data['day_n'];
						}
						if ( isset( $module_data['time'] ) ) {
							$scan_settings->time = $module_data['time'];
							$scan_report->time   = $module_data['time'];
						}
						$scan_report->save();
						$scan_settings->save();
					} else {
						// Default data for scan notification.
						$scan_notification->status = 'disabled';
						// @since 2.7.0 We can remove it in the next version.
						$scan_notification->dry_run = false;
						$scan_notification->save();
						// For scan report.
						$scan_report->status = 'disabled';
						// @since 2.7.0 We can remove it in the next version.
						$scan_report->dry_run = false;
						$scan_report->save();
					}
				} elseif ( 'iplockout' === $module ) {
					// Run Lockout submodules.
					foreach ( $controller as $lockout_controller ) {
						$lockout_controller->import_data( $module_data );
					}
					// For Notification and Report.
					if ( is_array( $module_data ) && array() !== $module_data ) {
						// Get string values for notification & report.
						if ( isset( $module_data['notification'] ) ) {
							$lockout_notification = new Firewall_Notification();
							if ( $lockout_notification->status !== $module_data['notification'] ) {
								$lockout_notification->status = $module_data['notification'];
							}
							if ( isset( $module_data['login_lockout_notification'] ) ) {
								$lockout_notification->configs['login_lockout'] = $module_data['login_lockout_notification'];
							}
							if ( isset( $module_data['ip_lockout_notification'] ) ) {
								$lockout_notification->configs['nf_lockout'] = $module_data['ip_lockout_notification'];
							}
							if ( isset( $module_data['cooldown_enabled'] ) ) {
								$lockout_notification->configs['limit'] = $module_data['cooldown_enabled'];
							}
							if ( isset( $module_data['cooldown_number_lockout'] ) ) {
								$lockout_notification->configs['threshold'] = $module_data['cooldown_number_lockout'];
							}
							if ( isset( $module_data['cooldown_period'] ) ) {
								$lockout_notification->configs['cool_off'] = $module_data['cooldown_period'];
							}
							if ( isset( $module_data['notification_subscribers'] ) && is_array( $module_data['notification_subscribers'] ) && array() !== $module_data['notification_subscribers'] ) {
								// Reset all recipients before.
								$lockout_notification->in_house_recipients  = array();
								$lockout_notification->out_house_recipients = array();
								foreach ( $module_data['notification_subscribers'] as $key => $subscribers ) {
									$lockout_notification->$key = $subscribers;
								}
							}

							$lockout_notification->save();
						}
						if ( $this->is_pro && isset( $module_data['report'] ) ) {
							$lockout_report = new Firewall_Report();
							if ( $lockout_report->status !== $module_data['report'] ) {
								$lockout_report->status = $module_data['report'];
							}
							if ( isset( $module_data['day'] ) ) {
								$lockout_report->day = $module_data['day'];
							}
							if ( isset( $module_data['report_frequency'] ) ) {
								$lockout_report->frequency = $module_data['report_frequency'];
							}
							if ( isset( $module_data['day_n'] ) ) {
								$lockout_report->day_n = (int) $module_data['day_n'];
							}
							if ( isset( $module_data['report_time'] ) ) {
								$lockout_report->time = $module_data['report_time'];
							}
							if ( isset( $module_data['report_subscribers'] ) && is_array( $module_data['report_subscribers'] ) && array() !== $module_data['report_subscribers'] ) {
								// Reset all recipients before.
								$lockout_report->in_house_recipients  = array();
								$lockout_report->out_house_recipients = array();
								foreach ( $module_data['report_subscribers'] as $key => $subscribers ) {
									$lockout_report->$key = $subscribers;
								}
							}
							if ( isset( $module_data['last_sent'] ) ) {
								$lockout_report->last_sent = $module_data['last_sent'];
							}
							// @since 2.7.0 Remove 'dry_run'-restoring.
							$lockout_report->save();
						}
					} else {
						// Default data for lockout notification.
						$lockout_notification         = new Firewall_Notification();
						$lockout_notification->status = 'disabled';
						// @since 2.7.0 We can remove it in the next version.
						$lockout_notification->dry_run = false;
						$lockout_notification->configs = array(
							'login_lockout' => false,
							'nf_lockout'    => false,
							'limit'         => false,
							'threshold'     => 3,
							'cool_off'      => 24,
						);
						$lockout_notification->save();
						// For lockout report.
						$lockout_report         = new Firewall_Report();
						$lockout_report->status = 'disabled';
						// @since 2.7.0 We can remove it in the next version.
						$lockout_report->dry_run   = false;
						$lockout_report->frequency = 'weekly';
						$lockout_report->day_n     = 1;
						$lockout_report->day       = 'sunday';
						$lockout_report->time      = '4:00';
						$lockout_report->save();
					}
				} elseif ( 'audit' === $module ) {
					// Import.
					$controller->import_data( $module_data );
					// Report.
					$audit_report = new Audit_Report();
					if ( is_array( $module_data ) && array() !== $module_data ) {
						if (
							$this->is_pro
							&& isset( $module_data['report'] )
							&& $audit_report->status !== $module_data['report']
						) {
							$audit_report->status = $module_data['report'];
						}
						if ( isset( $module_data['frequency'] ) ) {
							$audit_report->frequency = $module_data['frequency'];
						}
						if ( isset( $module_data['day_n'] ) ) {
							$audit_report->day_n = (int) $module_data['day_n'];
						}
						if ( isset( $module_data['day'] ) ) {
							$audit_report->day = $module_data['day'];
						}
						if ( isset( $module_data['time'] ) ) {
							$audit_report->time = $module_data['time'];
						}
						if ( isset( $module_data['subscribers'] ) && is_array( $module_data['subscribers'] ) && array() !== $module_data['subscribers'] ) {
							// Reset all recipients before.
							$audit_report->in_house_recipients  = array();
							$audit_report->out_house_recipients = array();
							foreach ( $module_data['subscribers'] as $key => $subscribers ) {
								$audit_report->$key = $subscribers;
							}
						}
						if ( isset( $module_data['last_sent'] ) ) {
							$audit_report->last_sent = $module_data['last_sent'];
						}
						// @since 2.7.0 Remove 'dry_run'-restoring.
					} else {
						// For audit report.
						$audit_report->status = 'disabled';
						// @since 2.7.0 We can remove it in the next version.
						$audit_report->dry_run   = false;
						$audit_report->frequency = 'weekly';
						$audit_report->day_n     = 1;
						$audit_report->day       = 'sunday';
						$audit_report->time      = '4:00';
					}

					$audit_report->save();
					/**
					 * If 'blocklist_monitor' module is activated, 'enabled' set as true.
					 */
				} elseif (
					'blocklist_monitor' === $module
					&& $this->is_pro
					&& isset( $module_data['status'] )
				) {
					// No need to import data. Just change status.
					( new Blocklist_Monitor() )->change_status( $module_data['status'] );
				} elseif (
					'pwned_passwords' === $module
					&& isset( $module_data['custom_message'] )
				) {
					$controller->import_data( $module_data );
				} elseif ( 'force_strong_password' === $module ) {
					$controller->import_data( $module_data );
				} elseif ( 'session_protection' === $module && $this->is_pro ) {
					$controller->import_data( $module_data );
				} elseif ( 'two_factor' === $module ) {
					$controller->import_data( $module_data );
				} elseif ( 'mask_login' === $module ) {
					$controller->import_data( $module_data );
				} elseif ( 'security_headers' === $module ) {
					$controller->import_data( $module_data );
				} elseif ( 'settings' === $module ) {
					$controller->import_data( $module_data );
				} elseif ( 'security_tweaks' === $module ) {
					// Import.
					$controller->import_data( $module_data );
					// Automate process.
					if ( 'migration' !== $request_reason ) {
						// 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.
						// If this is combined with mask login, then we need to redirect to new URL.
						// The automate function should return this.
						$tweak_class = wd_di()->get( Controller_Security_Tweaks::class );
						$need_reauth = $tweak_class->automate( $module_data, $request_reason );
					}
					// For Tweak notification.
					if ( is_array( $module_data ) && array() !== $module_data && isset( $module_data['notification'] ) ) {
						$tweak_notification = new Tweak_Reminder();
						if ( $tweak_notification->status !== $module_data['notification'] ) {
							$tweak_notification->status = $module_data['notification'];
						}

						if ( isset( $module_data['notification_repeat'] ) ) {
							// Temporary check for older versions.
							if ( is_bool( $module_data['notification_repeat'] ) ) {
								$tweak_notification->configs['reminder'] = $module_data['notification_repeat']
									? 'daily'
									: 'weekly';
							} elseif (
								is_string( $module_data['notification_repeat'] )
								&& in_array(
									$module_data['notification_repeat'],
									array(
										'daily',
										'weekly',
										'monthly',
									),
									true
								)
							) {
								$tweak_notification->configs['reminder'] = $module_data['notification_repeat'];
							} else {
								$tweak_notification->configs['reminder'] = 'weekly';
							}
						} else {
							$tweak_notification->configs['reminder'] = 'weekly';
						}
						if ( isset( $module_data['subscribers'] ) && is_array( $module_data['subscribers'] ) && array() !== $module_data['subscribers'] ) {
							// Reset all recipients before.
							$tweak_notification->in_house_recipients  = array();
							$tweak_notification->out_house_recipients = array();
							foreach ( $module_data['subscribers'] as $key => $subscribers ) {
								$tweak_notification->$key = $subscribers;
							}
						}
						if ( isset( $module_data['last_sent'] ) ) {
							$tweak_notification->last_sent = $module_data['last_sent'];
						}
						$tweak_notification->save();
					}
				}
			}
		}
		// We should disable quick setup.
		update_site_option( 'wp_defender_is_activated', 1 );

		return $need_reauth;
	}

	/**
	 * Parses data for import, preparing it for use in the system.
	 *
	 * @param  null|array $configs  Optional. The configurations to parse. If not provided, gathers current data.
	 *
	 * @return array Returns an array containing parsed data for import.
	 */
	public function parse_data_for_import( $configs = null ): array {
		if ( ! is_array( $configs ) || array() === $configs ) {
			$configs = $this->gather_data();
		}
		$strings = array();
		foreach ( $configs as $module => $module_data ) {
			$controller = $this->module_to_controller( $module, false );
			if ( ! is_object( $controller ) ) {
				// In free, when audit not present.
				$strings[ $module ][] = sprintf(
				/* translators: %s: Html for Pro-tag. */
					esc_html__( 'Inactive %s', 'defender-security' ),
					'<span class="sui-tag sui-tag-pro">Pro</span>'
				);
				continue;
			}
			$strings[ $module ] = $controller->export_strings();
		}

		return array(
			'configs' => $configs,
			'strings' => $strings,
		);
	}

	/**
	 * Converts a module name to its corresponding controller object.
	 *
	 * @param  string $module  The name of the module.
	 * @param  bool   $all_firewall_lockouts  Whether to return all firewall and lockout controllers.
	 *
	 * @return object|array|string Returns the corresponding controller object, an array of controllers, or an empty
	 *     string if not applicable.
	 */
	public function module_to_controller( string $module, bool $all_firewall_lockouts = false ) {
		switch ( $module ) {
			case 'security_tweaks':
				return new Controller_Security_Tweaks();
			case 'scan':
				return new Controller_Scan();
			case 'audit':
				return class_exists( Audit_Logging::class ) ? new Audit_Logging() : '';
			case 'iplockout':
				if ( $all_firewall_lockouts ) {
					return array(
						new Controller_Firewall(),
						new \WP_Defender\Controller\Login_Lockout(),
						new Nf_Lockout(),
						new Global_Ip(),
						new Blacklist(),
						new Controller_Ua_Lockout(),
						wd_di()->get( \WP_Defender\Controller\Antibot_Global_Firewall::class ),
					);
				} else {
					return new Controller_Firewall();
				}
			case 'settings':
				return new Main_Setting();
			case 'two_factor':
				return new Two_Factor();
			case 'mask_login':
				return new Controller_Mask_Login();
			case 'security_headers':
				return new Security_Headers();
			case 'blocklist_monitor':
				return new Blocklist_Monitor();
			case 'pwned_passwords':
				return new Controller_Password_Protection();
			case 'force_strong_password':
				return new Strong_Password_Controller();
			case 'session_protection':
				return new Session_Protection_Controller();
			default:
				return '';
		}
	}

	/**
	 * Checks if the provided configuration data has the new structure.
	 *
	 * @param  array $configs  The configuration data to check.
	 *
	 * @return bool Returns true if the new structure is used, otherwise false.
	 */
	public function check_for_new_structure( array $configs ): bool {
		return array_key_exists( 'subscribers', $configs['security_tweaks'] );
	}

	/**
	 * Verifies the integrity of configuration data.
	 *
	 * @param  array $data  The configuration data to verify.
	 *
	 * @return bool Returns true if the data is valid, otherwise false.
	 */
	public function verify_config_data( $data ): bool {
		if (
			! isset( $data['name'], $data['configs'], $data['strings'] )
			|| '' === $data['name'] || ! is_array( $data['strings'] ) || array() === $data['strings']
		) {
			return false;
		}

		return true;
	}

	/**
	 * Imports module strings for use in the system.
	 *
	 * @param  array $data  The data containing module strings to import.
	 *
	 * @return array Returns an array of imported module strings.
	 */
	public function import_module_strings( array $data ): array {
		if ( ! isset( $data['strings'] ) || ! is_array( $data['strings'] ) || array() === $data['strings'] ) {
			return array();
		}

		foreach ( $data['configs'] as $key => $config ) {
			if (
				'security_tweaks' === $key
				&& 'enabled' === $config['notification']
				&& 1 === ( is_array( $data['strings']['security_tweaks'] )
							|| $data['strings']['security_tweaks'] instanceof Countable ? count( $data['strings']['security_tweaks'] ) : 0 )
			) {
				$data['strings']['security_tweaks'][] = esc_html__( 'Email notifications active', 'defender-security' );
			} elseif ( 'scan' === $key ) {
				$data['strings']['scan'] = wd_di()->get( Controller_Scan::class )->config_strings(
					$config,
					$this->is_pro
				);
			} elseif ( 'iplockout' === $key ) {
				$data['strings']['iplockout'] = wd_di()->get( Controller_Firewall::class )->config_strings(
					$config,
					$this->is_pro
				);
			} elseif ( 'audit' === $key ) {
				// Additional check for Free version.
				$config                   = is_array( $config ) ? $config : array();
				$data['strings']['audit'] = wd_di()->get( Audit_Logging::class )->config_strings(
					$config,
					$this->is_pro
				);
			} elseif ( 'blocklist_monitor' === $key ) {
				// Additional check for Free version.
				$config                               = is_array( $config ) ? $config : array();
				$data['strings']['blocklist_monitor'] = $this->format_blocklist_monitor_strings(
					$config,
					$this->is_pro
				);
			} elseif ( 'pwned_passwords' === $key ) {
				$data['strings']['pwned_passwords'] = wd_di()->get( Controller_Password_Protection::class )->config_strings(
					$config,
					$this->is_pro
				);
			}
		}

		return $data['strings'];
	}

	/**
	 * Creates default module strings for the Basic configuration.
	 *
	 * @param  array $configs  The configuration data.
	 * @param  bool  $is_pro  Whether the current installation is a pro version.
	 *
	 * @return array Returns an array of default module strings.
	 */
	public function create_default_module_strings( array $configs, bool $is_pro ): array {
		$strings = array();
		foreach ( $configs as $key => $config ) {
			if ( 'security_tweaks' === $key ) {
				$strings['security_tweaks'] = wd_di()->get( Controller_Security_Tweaks::class )->config_strings(
					$config,
					$is_pro
				);
			} elseif ( 'scan' === $key ) {
				$strings['scan'] = wd_di()->get( Controller_Scan::class )->config_strings( $config, $is_pro );
			} elseif ( 'iplockout' === $key ) {
				$strings['iplockout'] = wd_di()->get( Controller_Firewall::class )->config_strings( $config, $is_pro );
			} elseif ( 'audit' === $key ) {
				$strings['audit'] = wd_di()->get( Audit_Logging::class )->config_strings( $config, $is_pro );
			} elseif ( 'two_factor' === $key ) {
				$strings['two_factor'][] = esc_html__( 'Inactive', 'defender-security' );
			} elseif ( 'mask_login' === $key ) {
				$strings['mask_login'][] = esc_html__( 'Inactive', 'defender-security' );
			} elseif ( 'security_headers' === $key ) {
				$strings['security_headers'][] = esc_html__( 'Active', 'defender-security' );
			} elseif ( 'blocklist_monitor' === $key ) {
				$strings['blocklist_monitor'] = wd_di()->get( Blocklist_Monitor::class )->config_strings(
					$config,
					$is_pro
				);
			} elseif ( 'pwned_passwords' === $key ) {
				$strings['pwned_passwords'][] = esc_html__( 'Inactive', 'defender-security' );
			} elseif ( 'force_strong_password' === $key ) {
				$strings['force_strong_password'][] = esc_html__( 'Inactive', 'defender-security' );
			} elseif ( 'session_protection' === $key ) {
				$strings['session_protection'][] = esc_html__( 'Inactive', 'defender-security' );
			}
		}

		return $strings;
	}

	/**
	 * Retrieves a decoded settings array from the options table.
	 *
	 * @param  string $key  The key under which the settings are stored.
	 *
	 * @return array Returns the decoded settings array.
	 */
	private function get_decoded_settings( string $key ): array {
		$data = get_site_option( $key );
		if ( is_string( $data ) && '' !== trim( $data ) ) {
			$decoded_data = json_decode( $data, true );
			if ( is_array( $decoded_data ) ) {
				return $decoded_data;
			}
		}

		return array();
	}

	/**
	 * Get settings of previous version.
	 *
	 * @return array
	 */
	private function get_prev_settings(): array {
		$arr = array();
		if ( $this->is_pro ) {
			$status         = (string) wd_di()->get( Blocklist_Monitor::class )->get_status();
			$arr['enabled'] = '1' === $status;
			$arr['status']  = $status;
		} else {
			$arr['enabled'] = false;
		}

		return array(
			// Different keys.
			'security_tweaks'   => $this->get_decoded_settings( 'wd_hardener_settings' ),
			'iplockout'         => $this->get_decoded_settings( 'wd_lockdown_settings' ),
			// Identical keys.
			'scan'              => $this->get_decoded_settings( 'wd_scan_settings' ),
			'audit'             => $this->get_decoded_settings( 'wd_audit_settings' ),
			'two_factor'        => $this->get_decoded_settings( 'wd_2auth_settings' ),
			'mask_login'        => $this->get_decoded_settings( 'wd_masking_login_settings' ),
			'security_headers'  => $this->get_decoded_settings( 'wd_security_headers_settings' ),
			'settings'          => $this->get_decoded_settings( 'wd_main_settings' ),
			'blocklist_monitor' => $arr,
		);
	}

	/**
	 * Converts a module name to a human-readable name.
	 *
	 * @param  string $module  The module name to convert.
	 *
	 * @return string Returns the human-readable name of the module.
	 */
	private function module_to_name( string $module ): string {
		switch ( $module ) {
			case 'security_tweaks':
				return esc_html__( 'Security Recommendations', 'defender-security' );
			case 'scan':
				return esc_html__( 'Malware Scanning', 'defender-security' );
			case 'audit':
				return Model_Audit_Logging::get_module_name();
			case 'iplockout':
				return esc_html__( 'Firewall', 'defender-security' );
			case 'settings':
				return esc_html__( 'Settings', 'defender-security' );
			case 'two_factor':
				return esc_html__( '2FA', 'defender-security' );
			case 'mask_login':
				return Model_Mask_Login::get_module_name();
			case 'security_headers':
				return Model_Security_Headers::get_module_name();
			case 'blocklist_monitor':
				return esc_html__( 'Blocklist Monitor', 'defender-security' );
			case 'pwned_passwords':
				return Model_Password_Protection::get_module_name();
			default:
				return '';
		}
	}

	/**
	 * Retrieves labels for a specific module.
	 *
	 * @param  string $module  The module for which to retrieve labels.
	 *
	 * @return array|void Returns an array of labels for the module, or void if the module does not exist.
	 */
	public function model_labels( string $module ) {
		switch ( $module ) {
			case 'security_tweaks':
				return array_merge(
					( new Model_Security_Tweaks() )->labels(),
					( new Tweak_Reminder() )->labels()
				);
			case 'scan':
				return array_merge(
					( new Model_Scan() )->labels(),
					( new Malware_Notification() )->labels(),
					( new Malware_Report() )->labels()
				);
			case 'audit':
				if ( class_exists( Model_Audit_Logging::class ) ) {
					return array_merge(
						( new Model_Audit_Logging() )->labels(),
						( new Audit_Report() )->labels()
					);
				}
				break;
			case 'iplockout':
				return array_merge(
					( new Model_Firewall() )->labels(),
					( new Model_Login_Lockout() )->labels(),
					( new Model_Notfound_Lockout() )->labels(),
					( new Model_Blacklist_Lockout() )->labels(),
					( new Firewall_Notification() )->labels(),
					( new Firewall_Report() )->labels(),
					( new Model_Ua_Lockout() )->labels()
				);
			case 'settings':
				return ( new Model_Main_Setting() )->labels();
			case 'two_factor':
				return ( new Model_Two_Fa() )->labels();
			case 'mask_login':
				return ( new Model_Mask_Login() )->labels();
			case 'security_headers':
				return ( new Model_Security_Headers() )->labels();
			case 'blocklist_monitor':
				// Separate method is not for this model.
				return ( new Blocklist_Monitor() )->labels();
			case 'pwned_passwords':
				return ( new Model_Password_Protection() )->labels();
			default:
				break;
		}
	}

	/**
	 * Parses data for display on the Hub.
	 *
	 * @return array Returns an array containing parsed data for the Hub.
	 */
	public function parse_data_for_hub(): array {
		$configs = $this->gather_data();
		$labels  = array();
		$strings = array();
		foreach ( $configs as $module => $module_data ) {
			$model_labels = $this->model_labels( $module );

			if ( is_array( $module_data ) && is_array( $model_labels ) ) {
				$labels[ $module ]['name'] = $this->module_to_name( $module );
				foreach ( $module_data as $key => $value ) {
					// Todo: update logic to import/export whitelisted/blocklisted countries via maxmind_license_key.
					if ( in_array( $key, array( 'geoIP_db', 'geodb_path' ), true ) ) {
						continue;
					}

					if ( array_key_exists( $key, $model_labels ) ) {
						$labels[ $module ]['value'][ $key ] = array(
							'name'  => $model_labels[ $key ],
							'value' => $value,
						);
					}
				}

				$controller = $this->module_to_controller( $module, false );
				if ( ! is_object( $controller ) ) {
					// In free, when audit not present.
					$strings[ $module ] = sprintf(
					/* translators: %s: Html for Pro-tag. */
						esc_html__( 'Inactive %s', 'defender-security' ),
						'<span class="sui-tag sui-tag-pro">Pro</span>'
					);
					continue;
				}
				$strings[ $module ] = $controller->export_strings();
			}
		}

		return array(
			'configs' => $configs,
			'labels'  => $labels,
			'strings' => $strings,
		);
	}

	/**
	 * Format strings of Block list Monitor config.
	 *
	 * @param  array $config  Saved config.
	 * @param  bool  $is_pro  User membership status.
	 *
	 * @return array
	 */
	private function format_blocklist_monitor_strings( array $config, bool $is_pro ): array {
		// If Block list Monitor is enable.
		if ( isset( $config['status'] ) && '1' === (string) $config['status'] ) {
			if ( $is_pro ) {
				$monitor = array( esc_html__( 'Active', 'defender-security' ) );
			} else {
				$monitor = array(
					sprintf(
					/* translators: %s: Html for Pro-tag. */
						esc_html__( 'Active %s', 'defender-security' ),
						'<span class="sui-tag sui-tag-pro">Pro</span>'
					),
				);
			}
		} elseif ( $is_pro ) {
			$monitor = array( esc_html__( 'Inactive', 'defender-security' ) );
		} else {
			$monitor = array(
				sprintf(
				/* translators: %s: Html for Pro-tag. */
					esc_html__( 'Inactive %s', 'defender-security' ),
					'<span class="sui-tag sui-tag-pro">Pro</span>'
				),
			);
		}

		return $monitor;
	}

	/**
	 * Prepares labels from configuration data.
	 *
	 * @param  array $configs  The configuration data.
	 *
	 * @return array Returns an array of prepared labels.
	 */
	public function prepare_config_labels( array $configs ): array {
		$labels = array();

		foreach ( $configs as $module => $module_data ) {
			$model_labels = $this->model_labels( $module );

			if ( is_array( $module_data ) && is_array( $model_labels ) ) {
				$labels[ $module ]['name'] = $this->module_to_name( $module );
				foreach ( $module_data as $key => $value ) {
					// Todo: update logic to import/export whitelisted/blocklisted countries via maxmind_license_key.
					if ( in_array( $key, array( 'geoIP_db', 'geodb_path' ), true ) ) {
						continue;
					}

					if ( array_key_exists( $key, $model_labels ) ) {
						$labels[ $module ]['value'][ $key ] = array(
							'name'  => $model_labels[ $key ],
							'value' => $value,
						);
					}
				}
			}
		}

		return $labels;
	}

	/**
	 * User enumeration options enabled list.
	 *
	 * @return array Return array of enabled user enumeration options.
	 */
	private function get_enabled_user_enums() {
		$prevent_enum_users = new Prevent_Enum_Users();

		return $prevent_enum_users->get_enabled_user_enums();
	}

	/**
	 * Security key all option.
	 *
	 * @return mixed All value of the "security key" feature of security tweak.
	 */
	private function get_security_key_all_options() {
		$security_key = new Security_Key();

		return $security_key->get_all_option();
	}
}
