Server IP : 104.21.14.48 / Your IP : 13.59.229.87 [ Web Server : Apache System : Linux b70eb322-3aee-0c53-7c82-0db91281f2c6.secureserver.net 6.1.90-1.el9.elrepo.x86_64 #1 SMP PREEMPT_DYNAMIC Thu May 2 12:09:22 EDT 2024 x86_64 User : root ( 0) PHP Version : 8.0.30.2 Disable Function : NONE Domains : 0 Domains MySQL : ON | cURL : ON | WGET : ON | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /var/chroot/var/www/wp-content/plugins/defender-security/src/ |
Upload File : |
<?php /** * This file contains the logic for updating the WP Defender plugin. * * @package WP_Defender */ namespace WP_Defender; use wpdb; use WP_User_Query; use WP_Filesystem_Base; use WP_Defender\Traits\IO; use WP_Defender\Traits\User; use WP_Defender\Component\Crypt; use WP_Defender\Component\Firewall; use WP_Defender\Component\Webauthn; use WP_Defender\Model\Notification; use Safe\Exceptions\SodiumException; use WP_Defender\Component\Feature_Modal; use WP_Defender\Controller\Data_Tracking; use WP_Defender\Component\Backup_Settings; use WP_Defender\Component\Legacy_Versions; use WP_Defender\Controller\Security_Tweaks; use WP_Defender\Model\Setting\Security_Headers; use WP_Defender\Model\Setting\Notfound_Lockout; use WP_Defender\Model\Notification\Audit_Report; use WP_Defender\Model\Setting\Global_Ip_Lockout; use WP_Defender\Component\Config\Config_Adapter; use WP_Defender\Integrations\MaxMind_Geolocation; use WP_Defender\Traits\Webauthn as Webauthn_Trait; use WP_Defender\Model\Notification\Malware_Report; use WP_Defender\Model\Notification\Tweak_Reminder; use WP_Defender\Component\Config\Config_Hub_Helper; use WP_Defender\Model\Notification\Firewall_Report; use WP_Defender\Model\Setting\Scan as Scan_Settings; use WP_Defender\Component\Two_Fa as Two_Fa_Component; use WP_Defender\Component\Two_Factor\Providers\Totp; use WP_Defender\Model\Setting\Two_Fa as Two_Fa_Settings; use WP_Defender\Component\Security_Tweaks\Security_Key; use WP_Defender\Model\Notification\Malware_Notification; use WP_Defender\Model\Notification\Firewall_Notification; use WP_Defender\Component\Two_Factor\Providers\Fallback_Email; use WP_Defender\Helper\Analytics\Firewall as Firewall_Analytics; use WP_Defender\Model\Setting\Blacklist_Lockout as Model_Blacklist_Lockout; use WP_Defender\Model\Setting\Firewall as Model_Firewall; use function WP_Filesystem; /** * Upgrade the WP Defender plugin to the latest version. */ class Upgrader { use User; use Webauthn_Trait; use IO; /** * Migrate old security headers from security tweaks. Trigger it once time. * * @return void */ public function migrate_security_headers(): void { $model = wd_di()->get( Security_Headers::class ); $option = get_site_option( $model->get_table() ); if ( empty( $option ) ) { // Part of Security tweaks data. $old_key = 'wd_hardener_settings'; $old_settings = get_site_option( $old_key ); if ( ! is_array( $old_settings ) ) { $old_settings = json_decode( $old_settings, true ); if ( is_array( $old_settings ) && isset( $old_settings['data'] ) && ! empty( $old_settings['data'] ) ) { // Exist 'X-Frame-Options'. if ( isset( $old_settings['data']['sh_xframe'] ) && ! empty( $old_settings['data']['sh_xframe'] ) ) { $header_data = $old_settings['data']['sh_xframe']; $mode = ( isset( $header_data['mode'] ) && ! empty( $header_data['mode'] ) ) ? strtolower( $header_data['mode'] ) : false; /** * Directive ALLOW-FROM is deprecated. If header directive is ALLOW-FROM then set 'sameorigin'. * * @since 2.5.0 */ if ( 'allow-from' === $mode ) { $model->sh_xframe_mode = 'sameorigin'; } elseif ( in_array( $mode, array( 'sameorigin', 'deny' ), true ) ) { $model->sh_xframe_mode = $mode; } $model->sh_xframe = true; } // Exist 'X-XSS-Protection'. if ( isset( $old_settings['data']['sh_xss_protection'] ) && ! empty( $old_settings['data']['sh_xss_protection'] ) ) { $header_data = $old_settings['data']['sh_xss_protection']; if ( isset( $header_data['mode'] ) && ! empty( $header_data['mode'] ) && in_array( $header_data['mode'], array( 'sanitize', 'block' ), true ) ) { $model->sh_xss_protection_mode = $header_data['mode']; $model->sh_xss_protection = true; } } // Exist 'X-Content-Type-Options'. if ( isset( $old_settings['data']['sh_content_type_options'] ) && ! empty( $old_settings['data']['sh_content_type_options'] ) ) { $header_data = $old_settings['data']['sh_content_type_options']; if ( isset( $header_data['mode'] ) && ! empty( $header_data['mode'] ) ) { $model->sh_content_type_options_mode = $header_data['mode']; $model->sh_content_type_options = true; } } // Exist 'Strict Transport'. if ( isset( $old_settings['data']['sh_strict_transport'] ) && ! empty( $old_settings['data']['sh_strict_transport'] ) ) { $header_data = $old_settings['data']['sh_strict_transport']; if ( isset( $header_data['hsts_preload'] ) && ! empty( $header_data['hsts_preload'] ) ) { $model->hsts_preload = (int) $header_data['hsts_preload']; } if ( isset( $header_data['include_subdomain'] ) && ! empty( $header_data['include_subdomain'] ) ) { $model->include_subdomain = in_array( $header_data['include_subdomain'], array( 'true', '1', 1 ), true ) ? 1 : 0; } if ( isset( $header_data['hsts_cache_duration'] ) && ! empty( $header_data['hsts_cache_duration'] ) ) { $model->hsts_cache_duration = $header_data['hsts_cache_duration']; } $model->sh_strict_transport = true; } // Exist 'Referrer Policy'. if ( isset( $old_settings['data']['sh_referrer_policy'] ) && ! empty( $old_settings['data']['sh_referrer_policy'] ) ) { $header_data = $old_settings['data']['sh_referrer_policy']; if ( isset( $header_data['mode'] ) && ! empty( $header_data['mode'] ) ) { $model->sh_referrer_policy_mode = $header_data['mode']; $model->sh_referrer_policy = true; } } // Exist 'Feature-Policy'. if ( isset( $old_settings['data']['sh_feature_policy'] ) && ! empty( $old_settings['data']['sh_feature_policy'] ) ) { $header_data = $old_settings['data']['sh_feature_policy']; if ( isset( $header_data['mode'] ) && ! empty( $header_data['mode'] ) ) { $mode = strtolower( $header_data['mode'] ); $model->sh_feature_policy_mode = $mode; if ( 'origins' === $mode && isset( $header_data['values'] ) && ! empty( $header_data['values'] ) ) { // The values differ from the values of the 'X-Frame-Options' key, because they may be an array. if ( is_array( $header_data['values'] ) ) { $model->sh_feature_policy_urls = implode( PHP_EOL, $header_data['values'] ); // Otherwise. } elseif ( is_string( $header_data['values'] ) ) { $urls = explode( ' ', $header_data['values'] ); $model->sh_feature_policy_urls = implode( PHP_EOL, $urls ); } } $model->sh_feature_policy = true; } } // Save. $model->save(); } } } } /** * If user upgrade from an older version to the latest version. * * @param mixed $current_version The current version. * * @return void */ public function maybe_show_new_features( $current_version ) { if ( false === $current_version ) { // Do nothing. return; } // Set the version where we have added the new feature. // Update it when you want to show modal on a specific version. if ( version_compare( $current_version, '2.4', '<' ) ) { update_site_option( 'wd_show_new_feature', true ); } } /** * Migrate configs for latest versions. * * @param mixed $current_version The current version. * * @return void * @since 2.4 */ public function migrate_configs( $current_version ) { if ( version_compare( $current_version, '2.2', '>=' ) && version_compare( $current_version, '2.4', '<' ) ) { $config_component = wd_di()->get( Backup_Settings::class ); $prev_data = $config_component->backup_data(); if ( empty( $prev_data ) ) { return; } $adapter = wd_di()->get( Config_Adapter::class ); $migrated_data = $adapter->upgrade( $prev_data ); $config_component->restore_data( $migrated_data, 'migration' ); // Hide Onboard page. update_site_option( 'wp_defender_shown_activator', true ); $configs = $config_component->get_configs(); if ( ! empty( $configs ) ) { foreach ( $configs as $k => $config ) { if ( $config_component->verify_config_data( $config ) && ! $config_component->check_for_new_structure( $config['configs'] ) ) { $new_data = $config; $new_data['configs'] = $adapter->upgrade( $config['configs'] ); // Import config 'strings' and the active tag if a config has it. if ( isset( $config['is_active'] ) ) { $new_data['is_active'] = $config['is_active']; } $new_data['strings'] = $config_component->import_module_strings( $new_data ); // Update config data. update_site_option( $k, $new_data ); } } } } // For older versions we do not use old models, e.g. for version < 2.2. So the default values will be used. } /** * Upgrade to version 2.4.2. * * @return void */ private function upgrade_2_4_2(): void { // Update Scan settings. $model_settings = wd_di()->get( Scan_Settings::class ); $option = get_site_option( $model_settings->get_table() ); if ( ! empty( $option ) && ! is_array( $option ) ) { $old_settings = json_decode( $option, true ); if ( is_array( $old_settings ) && isset( $old_settings['max_filesize'] ) ) { $model_settings->filesize = (int) $old_settings['max_filesize']; $model_settings->save(); } } // Update 'reminder_duration' value inside the Security Key tweak. $old_settings = get_site_option( 'wd_hardener_settings' ); if ( ! empty( $old_settings ) && ! is_array( $old_settings ) ) { $old_settings = json_decode( $old_settings, true ); $tweak_sec_key = wd_di()->get( Security_Key::class ); if ( is_array( $old_settings ) && isset( $old_settings['data']['securityReminderDuration'] ) && in_array( $old_settings['data']['securityReminderDuration'], $tweak_sec_key->reminder_frequencies(), true ) ) { $tweak_sec_key->update_option( 'reminder_duration', $old_settings['data']['securityReminderDuration'] ); } } } /** * Migrate value of scan setting from 'integrity_check' to 'check_core'. * * @return void * @since 2.4.7 */ private function migrate_scan_integrity_check(): void { $model = new Scan_Settings(); $model->check_core = (bool) $model->integrity_check; $model->save(); } /** * Run an upgrade/installation. * * @return void * @throws SodiumException If the encryption fails. */ public function run() { // Sometimes multiple requests come at the same time. So we will only count the web requests. if ( defined( 'DOING_AJAX' ) || defined( 'DOING_CRON' ) ) { return; } $db_version = get_site_option( 'wd_db_version' ); if ( empty( $db_version ) ) { update_site_option( 'wd_db_version', DEFENDER_DB_VERSION ); // Add the "What's new" modal only for fresh Pro v3.11.0 install. if ( defined( 'WP_DEFENDER_PRO' ) && WP_DEFENDER_PRO ) { update_site_option( Feature_Modal::FEATURE_SLUG, true ); } return; } if ( DEFENDER_DB_VERSION === $db_version ) { return; } $this->maybe_show_new_features( $db_version ); $this->migrate_configs( $db_version ); if ( version_compare( $db_version, '2.2.9', '<' ) ) { $this->migrate_security_headers(); } if ( version_compare( $db_version, '2.4.2', '<' ) ) { $this->upgrade_2_4_2(); } if ( version_compare( $db_version, '2.4.7', '<' ) ) { $this->index_database(); $this->migrate_scan_integrity_check(); } if ( version_compare( $db_version, '2.4.10', '<' ) ) { $this->upgrade_2_4_10(); } if ( version_compare( $db_version, '2.5.0', '<' ) ) { $this->upgrade_2_5_0(); } if ( version_compare( $db_version, '2.5.6', '<' ) ) { $this->upgrade_2_5_6(); } if ( version_compare( $db_version, '2.6.1', '<' ) ) { $this->upgrade_2_6_1(); } if ( version_compare( $db_version, '2.7.0', '<' ) ) { $this->upgrade_2_7_0(); } if ( version_compare( $db_version, '2.8.0', '<' ) ) { $this->upgrade_2_8_0(); } if ( version_compare( $db_version, '2.8.3', '<' ) ) { $this->upgrade_2_8_3(); } if ( version_compare( $db_version, '3.2.0', '<' ) ) { $this->upgrade_3_2_0(); } if ( version_compare( $db_version, '3.3.0', '<' ) ) { $this->upgrade_3_3_0(); } if ( version_compare( $db_version, '3.3.1', '<' ) ) { $this->upgrade_3_3_1(); } if ( version_compare( $db_version, '3.3.3', '<' ) ) { $this->upgrade_3_3_3(); } if ( version_compare( $db_version, '3.5.0', '<' ) ) { $this->upgrade_3_5_0(); } if ( version_compare( $db_version, '3.8.0', '<' ) ) { $this->upgrade_3_8_0(); } if ( version_compare( $db_version, '3.8.2', '<' ) ) { $this->upgrade_3_8_2(); } if ( version_compare( $db_version, '3.9.0', '<' ) ) { $this->upgrade_3_9_0(); } if ( version_compare( $db_version, '3.11.0', '<' ) ) { $this->upgrade_3_11_0(); } if ( version_compare( $db_version, '3.12.0', '<' ) ) { $this->upgrade_3_12_0(); } if ( version_compare( $db_version, '4.0.0', '<' ) ) { $this->upgrade_4_0_0(); } if ( version_compare( $db_version, '4.1.0', '<' ) ) { $this->upgrade_4_1_0(); } if ( version_compare( $db_version, '4.2.0', '<' ) ) { $this->upgrade_4_2_0(); } if ( version_compare( $db_version, '4.5.1', '<' ) ) { $this->upgrade_4_5_1(); } if ( version_compare( $db_version, '4.6.0', '<' ) ) { $this->upgrade_4_6_0(); } if ( version_compare( $db_version, '4.7.2', '<' ) ) { $this->upgrade_4_7_2(); } if ( version_compare( $db_version, '4.8.2', '<' ) ) { $this->upgrade_4_8_2(); } if ( version_compare( $db_version, '4.9.0', '<' ) ) { $this->upgrade_4_9_0(); } // This is not a new installation. Make a mark. defender_no_fresh_install(); // Don't run any function below this line. update_site_option( 'wd_db_version', DEFENDER_DB_VERSION ); } /** * Index necessary columns. * Sometimes this function call twice that's why we have to check index already exists or not. * `dbDelta` not work on `ALTER TABLE` query, so we had to use $wpdb->query(). * * @return void * @since 2.4.7 */ private function index_database(): void { global $wpdb; $wpdb->hide_errors(); $this->add_index_to_defender_email_log( $wpdb ); $this->add_index_to_defender_audit_log( $wpdb ); $this->add_index_to_defender_scan_item( $wpdb ); $this->add_index_to_defender_lockout_log( $wpdb ); $this->add_index_to_defender_lockout( $wpdb ); } /** * Add index to defender_email_log. * * @param wpdb $wpdb WordPress Database object. * * @return void * @since 2.4.7 */ private function add_index_to_defender_email_log( $wpdb ) { // Check index already exists or not. $result = $wpdb->get_row( "SHOW INDEX FROM {$wpdb->base_prefix}defender_email_log WHERE Key_name = 'source';", ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery if ( is_array( $result ) ) { return; } $wpdb->query( "ALTER TABLE {$wpdb->base_prefix}defender_email_log ADD INDEX `source` (`source`);" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery } /** * Add index to defender_audit_log. * * @param wpdb $wpdb WordPress Database object. * * @return void * @since 2.4.7 */ private function add_index_to_defender_audit_log( $wpdb ) { // Check index already exists or not. $result = $wpdb->get_row( "SHOW INDEX FROM {$wpdb->base_prefix}defender_audit_log WHERE Key_name = 'event_type';", ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery if ( is_array( $result ) ) { return; } $wpdb->query( "ALTER TABLE {$wpdb->base_prefix}defender_audit_log ADD INDEX `event_type` (`event_type`), ADD INDEX `action_type` (`action_type`), ADD INDEX `user_id` (`user_id`), ADD INDEX `context` (`context`), ADD INDEX `ip` (`ip`);" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery } /** * Add index to defender_scan_item. * * @param wpdb $wpdb WordPress Database object. * * @return void * @since 2.4.7 */ private function add_index_to_defender_scan_item( $wpdb ) { $table = $wpdb->base_prefix . 'defender_scan_item'; // Check index already exists or not. $result = $wpdb->get_row( "SHOW INDEX FROM {$wpdb->base_prefix}defender_scan_item WHERE Key_name = 'type';", ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery if ( is_array( $result ) ) { return; } $wpdb->query( "ALTER TABLE {$wpdb->base_prefix}defender_scan_item ADD INDEX `type` (`type`), ADD INDEX `status` (`status`);" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery } /** * Add index to defender_lockout_log. * * @param wpdb $wpdb WordPress Database object. * * @return void * @since 2.4.7 */ private function add_index_to_defender_lockout_log( $wpdb ) { // Check index already exists or not. $result = $wpdb->get_row( "SHOW INDEX FROM {$wpdb->base_prefix}defender_lockout_log WHERE Key_name = 'ip';", ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery if ( is_array( $result ) ) { return; } $wpdb->query( "ALTER TABLE {$wpdb->base_prefix}defender_lockout_log ADD INDEX `ip` (`ip`), ADD INDEX `type` (`type`), ADD INDEX `tried` (`tried`);" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery } /** * Add index to defender_lockout. * * @param wpdb $wpdb WordPress Database object. * * @return void * @since 2.4.7 */ private function add_index_to_defender_lockout( $wpdb ) { $table = $wpdb->base_prefix . 'defender_lockout'; // Check index already exists or not. $result = $wpdb->get_row( "SHOW INDEX FROM {$wpdb->base_prefix}defender_lockout WHERE Key_name = 'ip';", ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery if ( is_array( $result ) ) { return; } $wpdb->query( "ALTER TABLE {$wpdb->base_prefix}defender_lockout ADD INDEX `ip` (`ip`), ADD INDEX `status` (`status`), ADD INDEX `attempt` (`attempt`), ADD INDEX `attempt_404` (`attempt_404`);" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery } /** * Upgrade to 2.4.10. * * @return void * @since 2.4.10 */ private function upgrade_2_4_10(): void { $service = wd_di()->get( Backup_Settings::class ); $configs = Config_Hub_Helper::get_configs( $service ); $deprecated_keys = array( // Reason: updated or removed some tweak slugs. 'security_key', 'wp-rest-api', // Reason: moved the security headers to a separate module. 'sh-referrer-policy', 'sh-strict-transport', 'sh-xframe', 'sh-content-security', 'sh-content-type-options', 'sh-feature-policy', 'sh-xss-protection', ); foreach ( $configs as $key => $config ) { $is_updated = false; // Remove deprecated 'data' key inside Security tweaks. if ( isset( $config['configs']['security_tweaks']['data'] ) ) { unset( $configs[ $key ]['configs']['security_tweaks']['data'] ); $is_updated = true; } // Remove deprecated keys in 'issues'. if ( isset( $config['configs']['security_tweaks']['issues'] ) ) { foreach ( $config['configs']['security_tweaks']['issues'] as $iss_key => $issue ) { if ( in_array( $issue, $deprecated_keys, true ) ) { unset( $configs[ $key ]['configs']['security_tweaks']['issues'][ $iss_key ] ); $is_updated = true; } } } // In 'ignore'. if ( isset( $config['configs']['security_tweaks']['ignore'] ) ) { foreach ( $config['configs']['security_tweaks']['ignore'] as $ign_key => $issue ) { if ( in_array( $issue, $deprecated_keys, true ) ) { unset( $configs[ $key ]['configs']['security_tweaks']['ignore'][ $ign_key ] ); $is_updated = true; } } } // In 'fixed'. if ( isset( $config['configs']['security_tweaks']['fixed'] ) ) { foreach ( $config['configs']['security_tweaks']['fixed'] as $fix_key => $issue ) { if ( in_array( $issue, $deprecated_keys, true ) ) { unset( $configs[ $key ]['configs']['security_tweaks']['fixed'][ $fix_key ] ); $is_updated = true; } } } if ( $is_updated ) { update_site_option( $key, $configs[ $key ] ); Config_Hub_Helper::update_on_hub( $configs[ $key ] ); if ( isset( $config['is_active'] ) ) { $model = new Model\Setting\Security_Tweaks(); $model->issues = $configs[ $key ]['configs']['security_tweaks']['issues']; $model->ignore = $configs[ $key ]['configs']['security_tweaks']['ignore']; $model->fixed = $configs[ $key ]['configs']['security_tweaks']['fixed']; $model->save(); } } } } /** * Upgrade to 2.5.0. * * @return void * @since 2.5.0 */ private function upgrade_2_5_0(): void { $model = wd_di()->get( Security_Headers::class ); // Directive ALLOW-FROM is deprecated. If header directive is ALLOW-FROM then set 'sameorigin'. if ( isset( $model->sh_xframe_mode ) && 'allow-from' === $model->sh_xframe_mode ) { $model->sh_xframe_mode = 'sameorigin'; $model->save(); } /** * Uncheck 'File change detection' option if there was checked only child 'Scan theme files' option and save * settings. Also remove items for 'Scan theme files' without run Scan. */ $scan_settings = new Scan_Settings(); if ( $scan_settings->integrity_check && ! $scan_settings->check_core && ! $scan_settings->check_plugins ) { $scan_settings->integrity_check = false; $scan_settings->save(); } } /** * Forces the addition of default lockout exclusions to the Notfound Lockout settings. * * @return void */ private function force_nf_lockout_exclusions(): void { $nf_settings = new Notfound_Lockout(); $allowlist = $nf_settings->get_lockout_list( 'allowlist' ); $default_allowlist = array( '.css', '.js', '.map' ); $is_save = false; if ( ! empty( $allowlist ) ) { foreach ( $default_allowlist as $item ) { if ( ! in_array( $item, $allowlist, true ) ) { $allowlist[] = $item; $is_save = true; } } $nf_settings->whitelist = implode( "\n", $allowlist ); } else { $nf_settings->whitelist = ".css\n.js\n.map"; $is_save = true; } // Save it. if ( $is_save ) { $nf_settings->save(); } } /** * Upgrade to 2.5.6. * * @return void * @since 2.5.6 */ private function upgrade_2_5_6(): void { $adapted_component = wd_di()->get( Legacy_Versions::class ); $issue_list = $adapted_component->get_scan_issue_data(); $ignored_list = $adapted_component->get_scan_ignored_data(); if ( ! empty( $issue_list ) || ! empty( $ignored_list ) ) { $adapted_component->migrate_scan_data( $issue_list, $ignored_list ); $adapted_component->remove_old_scan_data( $issue_list, $ignored_list ); $adapted_component->change_onboarding_status(); } // Trigger recurring cron schedule for autogenerate security salt. $security_tweaks = wd_di()->get( Security_Tweaks::class ); $security_tweaks->get_security_key()->cron_schedule(); // Add some lockout extension to old installations forced. $this->force_nf_lockout_exclusions(); } /** * Updates the body of the scan error email template. * * @param mixed $model The model containing the email template configuration. * * @return void */ private function update_scan_error_send_body( $model ): void { if ( isset( $model->configs['template']['error']['body'] ) && ! empty( $model->configs['template']['error']['body'] ) ) { $needle = '{follow this link} and check the logs to see what casued the failure'; if ( false !== stripos( $model->configs['template']['error']['body'], $needle ) ) { $model->configs['template']['error']['body'] = str_replace( $needle, 'visit your site and run a manual scan', $model->configs['template']['error']['body'] ); $model->save(); } } } /** * Upgrade to 2.6.1. * * @return void * @since 2.6.1 */ private function upgrade_2_6_1(): void { // Update the title of the basic config. $config_component = wd_di()->get( Backup_Settings::class ); $configs = $config_component->get_configs(); if ( ! empty( $configs ) ) { foreach ( $configs as $k => $config ) { if ( 0 === strcmp( $config['name'], esc_html__( 'Basic config', 'defender-security' ) ) ) { $config['name'] = esc_html__( 'Basic Config', 'defender-security' ); update_site_option( $k, $config ); delete_site_transient( Config_Hub_Helper::CONFIGS_TRANSIENT_KEY ); break; } } } // Update scan emails for 'When failed to scan' option. $malware_notification = wd_di()->get( Malware_Notification::class ); $this->update_scan_error_send_body( $malware_notification ); $malware_report = wd_di()->get( Malware_Report::class ); $this->update_scan_error_send_body( $malware_report ); } /** * Update 2FA email template. * * @return void */ private function update_2fa_send_body(): void { $model = wd_di()->get( Two_Fa_Settings::class ); if ( isset( $model->email_body ) ) { $model->email_body = 'Hi {{display_name}}, Your temporary password is {{passcode}}. To finish logging in, copy and paste the temporary password into the Password field on the login screen.'; $model->save(); } } /** * Update Malware Scan email template. * * @return void * @since 2.7.0 */ private function update_malware_scan_send_body(): void { $models = array( wd_di()->get( Malware_Notification::class ), wd_di()->get( Malware_Report::class ), ); foreach ( $models as $model ) { $is_updated = false; if ( ! empty( $model->configs['template']['error']['body'] ) ) { $subject = $this->replace_scan_email_content_error( $model->configs['template']['error']['body'] ); if ( $model->configs['template']['error']['body'] !== $subject ) { $is_updated = true; $model->configs['template']['error']['body'] = $subject; } } if ( ! empty( $model->configs['template']['found']['body'] ) ) { $subject = $this->replace_scan_email_content_issue_found( $model->configs['template']['found']['body'] ); if ( $model->configs['template']['found']['body'] !== $subject ) { $is_updated = true; $model->configs['template']['found']['body'] = $subject; } } if ( ! empty( $model->configs['template']['not_found']['body'] ) ) { $subject = $this->replace_scan_email_content_issue_not_found( $model->configs['template']['not_found']['body'] ); if ( $model->configs['template']['not_found']['body'] !== $subject ) { $is_updated = true; $model->configs['template']['not_found']['body'] = $subject; } } if ( true === $is_updated ) { $model->save(); } } } /** * Update Malware Scan email template common parts. * * @param string $subject Email subject. * * @return string * @since 2.7.0 */ private function update_malware_scan_send_body_common( $subject ): string { $subject = preg_replace( '/(\R+)WP Defender here, reporting back from the front\./i', '', $subject ); return preg_replace( '/(\R+)Stay Safe,(\R+)WP Defender(\R+)WPMU DEV Superhero(\s*)$/i', '', $subject ); } /** * Upgrade to 2.7.0. * * @return void * @since 2.7.0 */ private function upgrade_2_7_0(): void { $malware_report = wd_di()->get( Malware_Report::class ); $scan_settings = wd_di()->get( Scan_Settings::class ); // Migrate data from Malware_Report to Scan settings. $scan_settings->scheduled_scanning = Notification::STATUS_ACTIVE === $malware_report->status; $scan_settings->frequency = $malware_report->frequency; $scan_settings->day = $malware_report->day; $scan_settings->day_n = $malware_report->day_n; $scan_settings->time = $malware_report->time; $scan_settings->save(); $this->update_2fa_send_body(); $this->update_malware_scan_send_body(); } /** * Update in_house recipients empty role. * * @return void * @since 2.8.0 */ private function update_in_house_recipients_empty_role(): void { $models = array( wd_di()->get( Audit_Report::class ), wd_di()->get( Firewall_Notification::class ), wd_di()->get( Firewall_Report::class ), wd_di()->get( Malware_Notification::class ), wd_di()->get( Malware_Report::class ), wd_di()->get( Tweak_Reminder::class ), ); foreach ( $models as $model ) { if ( ! empty( $model->in_house_recipients ) && is_array( $model->in_house_recipients ) ) { $is_updated = false; foreach ( $model->in_house_recipients as &$recipient ) { if ( empty( $recipient['role'] ) ) { $is_updated = true; $recipient['role'] = $this->get_current_user_role( $recipient['id'] ); } } if ( true === $is_updated ) { $model->save(); } } } } /** * Delete temp geolite file from /wp-admin directory. * * @return void * @since 2.8.0 */ private function delete_tmp_geolite_file(): void { $pattern = ABSPATH . 'wp-admin/geolite2-country*.tar.gz'; array_map( 'wp_delete_file', glob( $pattern ) ); } /** * Migrate old keys to checked options. * * @return void * @since 2.8.0 */ private function update_2fa_methods(): void { $settings = wd_di()->get( Two_Fa_Settings::class ); if ( $settings->enabled ) { $service = wd_di()->get( Two_Fa_Component::class ); $query = new WP_User_Query( array( 'blog_id' => 0, 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query array( 'key' => Totp::TOTP_AUTH_KEY, 'value' => true, ), ), 'fields' => 'ID', ) ); if ( $query->get_total() > 0 ) { // The data is independent of the user's data. $providers = $service->get_providers(); $totp_slug = Totp::$slug; // Add TOTP slug. $enabled_providers = array( $totp_slug ); // If 'Enable lost phone' option is checked then we add the slug of the Email provider too. if ( true === $settings->lost_phone ) { $enabled_providers[] = Fallback_Email::$slug; } // Add slugs to the list of the enabled providers. $enabled_providers = array_intersect( $enabled_providers, array_keys( $providers ) ); foreach ( $query->get_results() as $user_id ) { // If 2FA module is configured for the current user. if ( $service->is_enabled_otp_for_user( $user_id ) ) { update_user_meta( $user_id, Two_Fa_Component::ENABLED_PROVIDERS_USER_KEY, $enabled_providers ); // Set TOTP as the default auth provider. update_user_meta( $user_id, Two_Fa_Component::DEFAULT_PROVIDER_USER_KEY, $totp_slug ); } } } } } /** * Upgrade to 2.8.0. * * @return void * @since 2.8.0 */ private function upgrade_2_8_0(): void { $this->update_in_house_recipients_empty_role(); $this->delete_tmp_geolite_file(); $this->update_2fa_methods(); } /** * Add column country_iso_code and index to table. * * @return void * @since 2.8.1 */ private function add_country_iso_code_column(): void { if ( ! function_exists( 'maybe_add_column' ) ) { require_once ABSPATH . 'wp-admin/includes/upgrade.php'; } global $wpdb; $table_name = $wpdb->base_prefix . 'defender_lockout_log'; $column_name = 'country_iso_code'; $create_ddl = "ALTER TABLE {$table_name} ADD {$column_name} CHAR(2) DEFAULT NULL"; maybe_add_column( $table_name, $column_name, $create_ddl ); $prev_val = $wpdb->hide_errors(); $wpdb->query( "CREATE INDEX `country_iso_code` ON {$wpdb->base_prefix}defender_lockout_log (`country_iso_code`)" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->show_errors( $prev_val ); } /** * Upgrade to 2.8.3. * * @return void * @since 2.8.3 */ private function upgrade_2_8_3(): void { $this->add_country_iso_code_column(); } /** * Upgrade to 3.2.0. * * @return void * @since 3.2.0 */ private function upgrade_3_2_0(): void { $model = wd_di()->get( Two_Fa_Settings::class ); if ( ! empty( $model->app_title ) ) { $model->app_title = wp_specialchars_decode( $model->app_title, ENT_QUOTES ); } $model->custom_graphic_type = Two_Fa_Settings::CUSTOM_GRAPHIC_TYPE_UPLOAD; $model->save(); } /** * Upgrade to 3.3.0. * * @return void * @since 3.3.0 */ private function upgrade_3_3_0(): void { $this->update_webauthn_user_handle(); $this->add_ua_lockout_to_firewall_notification(); } /** * Update webauthn 'userHandle' to make it independent of salt keys. * * @return void * @since 3.3.0 */ private function update_webauthn_user_handle(): void { global $wpdb; $service = wd_di()->get( Webauthn::class ); if ( is_multisite() ) { $offset = 0; $limit = 100; // Variable within condition is for comparison. while ( $blogs = $wpdb->get_results( $wpdb->prepare( "SELECT {$wpdb->blogs} FROM {$wpdb->blogs} LIMIT %d, %d", $offset, $limit ), ARRAY_A ) ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery, Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition if ( ! empty( $blogs ) && is_array( $blogs ) ) { foreach ( $blogs as $blog ) { switch_to_blog( $blog['blog_id'] ); $this->update_webauthn_user_handle_core( $service ); restore_current_blog(); } } $offset += $limit; } } else { $this->update_webauthn_user_handle_core( $service ); } } /** * Core method for updating webauthn 'userHandle' to make it independent of salt keys. * * @param object $service Webauthn service instance. * * @return void * @since 3.3.0 */ private function update_webauthn_user_handle_core( object $service ) { $data = get_option( $this->option_prefix . $service::CREDENTIAL_OPTION_KEY ); $data = is_string( $data ) ? json_decode( $data, true ) : array(); delete_option( $this->option_prefix . $service::CREDENTIAL_OPTION_KEY ); if ( ! is_array( $data ) || 0 === count( $data ) ) { return; } $users = get_users( array( 'fields' => array( 'ID', 'user_login', 'display_name' ) ) ); foreach ( $users as $user ) { if ( empty( $data ) ) { break; } $user_credentials = array(); foreach ( $data as $key => $item ) { $old_hash = hash( 'sha256', $user->user_login . '-' . $user->display_name . '-' . AUTH_SALT ); $old_base64_hash = base64_encode( $old_hash ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode $old_base64_hash = preg_replace( '/\=+$/', '', $old_base64_hash ); if ( isset( $item['credential_source']['userHandle'] ) && $item['credential_source']['userHandle'] === $old_base64_hash ) { $new_hash = $this->get_user_hash( $user->user_login ); $new_base64_hash = base64_encode( $new_hash ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode $new_base64_hash = preg_replace( '/\=+$/', '', $new_base64_hash ); $item['user'] = $new_hash; $item['credential_source']['userHandle'] = $new_base64_hash; $user_credentials[ $key ] = $item; unset( $data[ $key ] ); } } if ( ! empty( $user_credentials ) ) { $service->setCredentials( (int) $user->ID, $user_credentials ); } } } /** * Add the ability to send notifications about UA-lockout. * * @return void */ private function add_ua_lockout_to_firewall_notification(): void { $model = wd_di()->get( Firewall_Notification::class ); if ( ! isset( $model->configs['ua_lockout'] ) ) { $model->configs['ua_lockout'] = false; $model->save(); } } /** * Upgrade to 3.3.1: extra security steps. * * @return void */ private function upgrade_3_3_1(): void { // Update tokens. $query = new WP_User_Query( array( 'blog_id' => 0, 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query array( 'key' => Two_Fa_Component::TOKEN_USER_KEY, 'compare' => 'EXISTS', ), ), 'fields' => 'ID', ) ); if ( $query->get_total() > 0 ) { // Hashed tokens. foreach ( $query->get_results() as $user_id ) { $token = bin2hex( Crypt::random_bytes( 32 ) ); update_user_meta( $user_id, Two_Fa_Component::TOKEN_USER_KEY, wp_hash( $user_id . $token ) ); } } // Update fallback codes. $query = new WP_User_Query( array( 'blog_id' => 0, 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query array( 'key' => Fallback_Email::FALLBACK_BACKUP_CODE_KEY, 'compare' => 'EXISTS', ), ), 'fields' => 'ID', ) ); if ( $query->get_total() > 0 ) { // Hashed codes. foreach ( $query->get_results() as $user_id ) { $backup_code = get_user_meta( $user_id, Fallback_Email::FALLBACK_BACKUP_CODE_KEY, true ); if ( ! empty( $backup_code ) && isset( $backup_code['code'], $backup_code['time'] ) ) { update_user_meta( $user_id, Fallback_Email::FALLBACK_BACKUP_CODE_KEY, array( 'code' => wp_hash( $backup_code['code'] ), 'time' => $backup_code['time'], ) ); } } } } /** * Upgrade to 3.3.3: update 2FA flow for secret keys. * * @return void * @throws SodiumException If the encryption fails. * @since 3.3.3 */ private function upgrade_3_3_3(): void { // Create a file with a random key if it doesn't exist. if ( ( new Crypt() )->create_key_file() ) { // Get user data. $query = new WP_User_Query( array( 'blog_id' => 0, 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query array( 'key' => Totp::TOTP_SODIUM_SECRET_KEY, 'compare' => 'EXISTS', ), ), 'fields' => 'ID', ) ); if ( $query->get_total() > 0 ) { $service = wd_di()->get( Two_Fa_Component::class ); foreach ( $query->get_results() as $user_id ) { // If we have the old states (cleartext, pub key) then re-encrypt via Sodium and save it. if ( ! $service->maybe_update( $user_id ) ) { // Otherwise just encrypt it via Sodium. $plaintext = defender_generate_random_string( Totp::TOTP_LENGTH, TOTP::TOTP_CHARACTERS ); $new_key = Crypt::get_encrypted_data( $plaintext ); if ( ! is_wp_error( $new_key ) ) { // Remove an old key. delete_user_meta( $user_id, Totp::TOTP_SECRET_KEY ); // Update a new one. update_user_meta( $user_id, Totp::TOTP_SODIUM_SECRET_KEY, $new_key ); } } } } } } /** * Upgrade to 3.5.0: Delete config transient. * * @return void */ private function upgrade_3_5_0(): void { delete_site_transient( Config_Hub_Helper::CONFIGS_TRANSIENT_KEY ); } /** * Upgrade to 3.8.0: Remove permanently banned ip record from `defender_lockout` table. * * @return void * @since 3.8.0 */ private function upgrade_3_8_0(): void { global $wpdb; $blacklist_settings = new Model_Blacklist_Lockout(); $blacklist = $blacklist_settings->get_list(); $offset = 0; $length = 100; $table = $wpdb->base_prefix . 'defender_lockout'; // Variable within condition is for comparison. while ( $blacklist_chunk = array_slice( $blacklist, $offset, $length ) ) { // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition $sql = "DELETE FROM {$table} WHERE status = 'blocked' AND ip IN (" . implode( ', ', array_fill( 0, count( $blacklist_chunk ), '%s' ) ) . ')'; $query = call_user_func_array( array( $wpdb, 'prepare' ), array_merge( array( $sql ), $blacklist_chunk ) ); // SQL is prepared here. So we don't need to prepare it again. $wpdb->query( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery $offset += $length; } $this->update_malware_notification_config(); $this->update_malware_notification_setting(); } /** * Update malware notification config. * * @return void * @since 3.8.0 */ private function update_malware_notification_config(): void { $service = wd_di()->get( Backup_Settings::class ); $configs = Config_Hub_Helper::get_configs( $service ); foreach ( $configs as $key => $config ) { $is_updated = false; if ( isset( $config['configs']['scan']['email_subject_issue_found'] ) ) { $subject = $this->replace_scan_email_subject_issue_found( $config['configs']['scan']['email_subject_issue_found'] ); $configs[ $key ]['configs']['scan']['email_subject_issue_found'] = $subject; $is_updated = true; } if ( isset( $config['configs']['scan']['email_content_issue_found'] ) ) { $content = $this->replace_scan_email_content_issue_found( $config['configs']['scan']['email_content_issue_found'] ); $content = $this->replace_scan_email_content_issue_found_new( $content ); $configs[ $key ]['configs']['scan']['email_content_issue_found'] = $content; $is_updated = true; } if ( isset( $config['configs']['scan']['email_content_issue_not_found'] ) ) { $content = $this->replace_scan_email_content_issue_not_found( $config['configs']['scan']['email_content_issue_not_found'] ); $configs[ $key ]['configs']['scan']['email_content_issue_not_found'] = $content; $is_updated = true; } if ( isset( $config['configs']['scan']['email_content_error'] ) ) { $content = $this->replace_scan_email_content_error( $config['configs']['scan']['email_content_error'] ); $configs[ $key ]['configs']['scan']['email_content_error'] = $content; $is_updated = true; } if ( true === $is_updated ) { update_site_option( $key, $configs[ $key ] ); Config_Hub_Helper::update_on_hub( $configs[ $key ] ); } } delete_site_transient( Config_Hub_Helper::CONFIGS_TRANSIENT_KEY ); } /** * Update malware notification setting. * * @return void * @since 3.8.0 */ private function update_malware_notification_setting(): void { $models = array( wd_di()->get( Malware_Notification::class ), wd_di()->get( Malware_Report::class ), ); foreach ( $models as $model ) { $is_updated = false; if ( isset( $model->configs['template']['found']['subject'] ) ) { $subject = $this->replace_scan_email_subject_issue_found( $model->configs['template']['found']['subject'] ); $model->configs['template']['found']['subject'] = $subject; $is_updated = true; } if ( isset( $model->configs['template']['found']['body'] ) ) { $content = $this->replace_scan_email_content_issue_found( $model->configs['template']['found']['body'] ); $content = $this->replace_scan_email_content_issue_found_new( $content ); $model->configs['template']['found']['body'] = $content; $is_updated = true; } if ( isset( $model->configs['template']['not_found']['body'] ) ) { $content = $this->replace_scan_email_content_issue_not_found( $model->configs['template']['not_found']['body'] ); $model->configs['template']['not_found']['body'] = $content; $is_updated = true; } if ( isset( $model->configs['template']['error']['body'] ) ) { $content = $this->replace_scan_email_content_error( $model->configs['template']['error']['body'] ); $model->configs['template']['error']['body'] = $content; $is_updated = true; } if ( true === $is_updated ) { $model->save(); } } } /** * Replace scan issue found email subject. * * @param string $subject Email subject. * * @return string * @since 3.8.0 */ private function replace_scan_email_subject_issue_found( string $subject ): string { $subject = str_replace( esc_html__( 'Scan of {SITE_URL} complete.', 'defender-security' ), esc_html__( 'Malware Scan of {SITE_URL} is complete.', 'defender-security' ), $subject ); return str_replace( esc_html__( '{ISSUES_COUNT} issues found.', 'defender-security' ), esc_html__( '{ISSUES_COUNT} issue(s) found.', 'defender-security' ), $subject ); } /** * Replace scan issue found email content. * * @param string $content Email content. * * @return string * @since 3.8.0 */ private function replace_scan_email_content_issue_found( string $content ): string { $content = $this->update_malware_scan_send_body_common( $content ); $content = preg_replace( "/I(’|'|\\\')ve finished scanning \{SITE_URL\} for vulnerabilities and I found \{ISSUES_COUNT\} issues that you should take a closer look at!/i", '{ISSUES_COUNT} vulnerabilities were identified for {SITE_URL} during a Malware Scan. See details for each issue below.', $content ); return preg_replace( '/(\R+){ISSUES_LIST}/i', "\n\n{ISSUES_LIST}", $content ); } /** * Replace scan issue found email with new content. * * @param string $content Email content. * * @return string * @since 3.8.0 */ private function replace_scan_email_content_issue_found_new( string $content ): string { $content = str_replace( esc_html__( '{ISSUES_COUNT} vulnerabilities were identified for {SITE_URL} during a Malware Scan.', 'defender-security' ), esc_html__( 'Malware Scan identified {ISSUES_COUNT} issue(s) on {SITE_URL}.', 'defender-security' ), $content ); return str_replace( esc_html__( 'See details for each issue below.', 'defender-security' ), esc_html__( 'The identified issue(s) is/are listed below.', 'defender-security' ), $content ); } /** * Replace scan issue not found email content. * * @param string $content Email content. * * @return string * @since 3.8.0 */ private function replace_scan_email_content_issue_not_found( string $content ): string { $content = $this->update_malware_scan_send_body_common( $content ); $content = preg_replace( "/I(’|'|\\\')ve finished scanning \{SITE_URL\} for vulnerabilities and I found nothing\. Well done for running such a tight ship!/i", 'No vulnerabilities have been found for {SITE_URL}.', $content ); return preg_replace( "/(\R+)Keep up the good work! With regular security scans and a well-hardened installation you\'ll be just fine\./i", '', $content ); } /** * Replace scan error email content. * * @param string $content Email content. * * @return string * @since 3.8.0 */ private function replace_scan_email_content_error( string $content ): string { $content = $this->update_malware_scan_send_body_common( $content ); return str_replace( 'I couldn', 'We couldn', $content ); } /** * Upgrade to 3.8.2: Check XSS in existing configs. * * @return void */ private function upgrade_3_8_2(): void { $service = wd_di()->get( Backup_Settings::class ); $configs = Config_Hub_Helper::get_configs( $service ); foreach ( $configs as $key => $config ) { $is_updated = false; if ( isset( $config['name'] ) ) { $prev_name = $config['name']; // Sanitize data. $new_name = sanitize_text_field( $config['name'] ); if ( $prev_name !== $new_name ) { $is_updated = true; } $prev_desc = $config['description']; $new_desc = empty( $config['description'] ) ? '' : sanitize_textarea_field( $config['description'] ); if ( $prev_desc !== $new_desc ) { $is_updated = true; } } if ( true === $is_updated ) { update_site_option( $key, $configs[ $key ] ); Config_Hub_Helper::update_on_hub( $configs[ $key ] ); } } delete_site_transient( Config_Hub_Helper::CONFIGS_TRANSIENT_KEY ); } /** * Upgrade to 3.9.0. * * @return void */ private function upgrade_3_9_0(): void { global $wpdb; $prev_val = $wpdb->hide_errors(); // Changes for Lockout table. $wpdb->query( "ALTER TABLE{$wpdb->base_prefix}defender_lockout MODIFY COLUMN ip VARCHAR(45)" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery // Changes for Lockout log table. $wpdb->query( "ALTER TABLE{$wpdb->base_prefix}defender_lockout_log MODIFY COLUMN ip VARCHAR(45)" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery // Changes for Audit log table. $wpdb->query( "ALTER TABLE{$wpdb->base_prefix}defender_audit_log MODIFY COLUMN ip VARCHAR(45)" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->show_errors( $prev_val ); } /** * Upgrade to 3.11.0. * * @return void */ private function upgrade_3_11_0(): void { // Add the "What's new" modal. update_site_option( Feature_Modal::FEATURE_SLUG, true ); // Move Global IP settings. $option = get_site_option( wd_di()->get( Model_Blacklist_Lockout::class )->get_table() ); if ( ! empty( $option ) && is_string( $option ) ) { $old_settings = json_decode( $option, true ); if ( is_array( $old_settings ) && isset( $old_settings['global_ip_list'] ) ) { $model_global_ip = wd_di()->get( Global_Ip_Lockout::class ); $model_global_ip->enabled = (bool) $old_settings['global_ip_list']; $model_global_ip->save(); } } } /** * Upgrade to 3.12.0. * * @return void */ private function upgrade_3_12_0(): void { delete_site_transient( Config_Hub_Helper::CONFIGS_TRANSIENT_KEY ); $service = wd_di()->get( Backup_Settings::class ); $configs = Config_Hub_Helper::get_configs( $service ); foreach ( $configs as $key => &$config ) { if ( ! is_array( $config ) ) { continue; } $config['is_removable'] = $config['is_removable'] ?? true; // Update config data. update_site_option( $key, $config ); } set_site_transient( Config_Hub_Helper::CONFIGS_TRANSIENT_KEY, $configs, Config_Hub_Helper::CONFIGS_TRANSIENT_TIME ); update_site_option( Config_Hub_Helper::CONFIGS_TRANSIENT_TIME_KEY, time() ); // Clear Global IP schedule hook to update event frequency. wp_clear_scheduled_hook( 'wpdef_fetch_global_ip_list' ); } /** * Upgrade to 4.0.0: Creates quarantine table. * * @return void */ private function upgrade_4_0_0(): void { if ( class_exists( 'WP_Defender\Controller\Quarantine' ) ) { $bootstrap = wd_di()->get( Bootstrap::class ); $bootstrap->create_table_quarantine(); } update_site_option( Feature_Modal::FEATURE_SLUG, true ); } /** * Upgrade to 4.1.0. * * @return void */ private function upgrade_4_1_0() { // Remove Maxmind DB directory from multisite subsites. if ( is_multisite() ) { global $wpdb, $wp_filesystem; if ( is_null( $wp_filesystem ) ) { if ( ! function_exists( 'WP_Filesystem' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; } WP_Filesystem(); } if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) { return; } $main_site_id = get_main_site_id(); $offset = 0; $limit = 100; // Variable within condition is for comparison. while ( $blogs = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery, Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition $wpdb->prepare( "SELECT blog_id FROM {$wpdb->blogs} WHERE blog_id != %d LIMIT %d, %d", $main_site_id, $offset, $limit ), ARRAY_A ) ) { if ( ! empty( $blogs ) && is_array( $blogs ) ) { foreach ( $blogs as $blog ) { switch_to_blog( $blog['blog_id'] ); $maxmind_dir = $this->get_tmp_path() . DIRECTORY_SEPARATOR . MaxMind_Geolocation::DB_DIRECTORY; $wp_filesystem->delete( $maxmind_dir, true ); restore_current_blog(); } } $offset += $limit; } } } /** * Upgrade to 4.2.0. * * @return void */ private function upgrade_4_2_0(): void { // Add the Tracking modal. update_site_option( Data_Tracking::TRACKING_SLUG, true ); } /** * Upgrade to 4.5.1. * * @return void */ private function upgrade_4_5_1(): void { $service = wd_di()->get( Firewall::class ); $service->auto_switch_ip_detection_option(); $service->maybe_show_misconfigured_ip_detection_option_notice(); } /** * Upgrade to 4.6.0. * * @return void */ private function upgrade_4_6_0(): void { // Create Unlockout table. $bootstrap = wd_di()->get( Bootstrap::class ); $bootstrap->create_table_unlockout(); } /** * Upgrade to 4.7.2. * * @return void */ private function upgrade_4_7_2(): void { $model = wd_di()->get( Two_Fa_Settings::class ); if ( isset( $model->email_body ) ) { $model->email_body = 'Hi {{display_name}}, Your temporary password is {{passcode}} To complete your login, copy and paste the temporary password into the Password field on the login screen.'; $model->save(); } } /** * Upgrade to 4.8.2. * * @return void */ private function upgrade_4_8_2(): void { $xff = defender_get_data_from_request( 'HTTP_X_FORWARDED_FOR', 's' ); if ( ! ( is_string( $xff ) && 0 < strlen( $xff ) ) && Firewall::is_switched_ip_detection_notice( Firewall::IP_DETECTION_XFF_SHOW_SLUG ) ) { delete_site_option( Firewall::IP_DETECTION_XFF_SHOW_SLUG ); } } /** * Upgrade to 4.9.0. * * @return void */ private function upgrade_4_9_0(): void { $model = wd_di()->get( Model_Firewall::class ); $model->ip_detection_type = 'manual'; $model->save(); // Add the "What's new" modal. update_site_option( Feature_Modal::FEATURE_SLUG, true ); // Add tracking. $model = wd_di()->get( Model_Firewall::class ); $firewall_analytics = wd_di()->get( Firewall_Analytics::class ); $detection_method = Firewall_Analytics::get_detection_method_label( $model->ip_detection_type, $model->http_ip_header ); $firewall_analytics->track_feature( Firewall_Analytics::EVENT_IP_DETECTION, array( Firewall_Analytics::PROP_IP_DETECTION => $detection_method ) ); } }