PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.3.0
Defender Security – Malware Scanner, Login Security & Firewall v6.3.0
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
← All changes | src/class-upgrader.php +237 -8 6.0.0 → 6.3.0 View file →
@@ -347,9 +347,9 @@
347 347 }
348 348
349 349 if ( DEFENDER_DB_VERSION === $db_version ) {
350 350 // Check if we need to create pro tables (Free to Pro upgrade scenario).
351 - if ( class_exists( Bootstrap::class ) && method_exists( Bootstrap::class, 'create_database_tables' ) && ! get_site_option( 'wd_pro_tables_created' ) ) {
351 + if ( class_exists( Bootstrap::class ) && method_exists( Bootstrap::class, 'create_database_tables' ) && false === (bool) get_site_option( 'wd_pro_tables_created', false ) ) {
352 352 wd_di()->get( Bootstrap::class )->create_database_tables();
353 353 }
354 354 return;
355 355 }
@@ -480,8 +480,14 @@
480 480 }
481 481 if ( version_compare( $db_version, '6.0.0', '<' ) ) {
482 482 $this->upgrade_6_0_0();
483 483 }
484 + if ( version_compare( $db_version, '6.1.0', '<' ) ) {
485 + $this->upgrade_6_1_0();
486 + }
487 + if ( version_compare( $db_version, '6.2.0', '<' ) ) {
488 + $this->upgrade_6_2_0();
489 + }
484 490 // This is not a new installation. Make a mark.
485 491 defender_no_fresh_install();
486 492 // Don't run any function below this line.
487 493 update_site_option( 'wd_db_version', DEFENDER_DB_VERSION );
@@ -1990,10 +1996,8 @@
1990 1996 // Add the "What's new" modal.
1991 1997 update_site_option( Feature_Modal::FEATURE_SLUG, true );
1992 1998 // Add Cloudflare challenge-platform to 404 whitelist.
1993 1999 $this->force_nf_lockout_exclusions();
1994 - // Remove the prev Breadcrumbs.
1995 - wd_di()->get( \WP_Defender\Component\Breadcrumbs::class )->delete_previous_meta();
1996 2000 }
1997 2001
1998 2002 /**
1999 2003 * Migrates the uninstall settings from v5.11 to v6.0.
@@ -2046,15 +2050,228 @@
2046 2050 $bootstrap->create_table_quarantine();
2047 2051 $this->combine_all_preserve_settings_to_one();
2048 2052 $this->combine_all_file_change_detection_settings_to_one();
2049 2053 $this->update_issues_report_email_template();
2054 + $this->fix_scheduled_scanning_left_disabled();
2050 2055 $this->migrate_tweak_reminder_schedule();
2051 2056 $this->migrate_two_fa_force_auth_roles();
2052 - // Add the "What's new" modal.
2053 - update_site_option( Feature_Modal::FEATURE_SLUG, true );
2057 + // Add the "What's new" modal only when landing on the exact release it belongs to,
2058 + // so it does not persist into later patch/minor releases (e.g. 6.0.1, 6.1).
2059 + Feature_Modal::maybe_enable_welcome_modal();
2054 2060 }
2055 2061
2056 2062 /**
2063 + * Upgrade to 6.1.0: Fix Tweak Reminder est_timestamp left as 0 by the 6.0.0 migration.
2064 + * Also sync all report schedules to match Malware Report.
2065 + *
2066 + * @return void
2067 + */
2068 + private function upgrade_6_1_0(): void {
2069 + $model = wd_di()->get( Tweak_Reminder::class );
2070 + if ( 'report' === $model->type && (int) $model->est_timestamp <= 0 ) {
2071 + // Calling save() recovers the correct state: last_sent is
2072 + // reset to now if it is 0, then est_timestamp is recomputed via get_next_run().
2073 + $model->save();
2074 + }
2075 +
2076 + $this->sync_all_report_schedules();
2077 + }
2078 +
2079 + /**
2080 + * Upgrade to 6.2.0.
2081 + *
2082 + * @return void
2083 + */
2084 + private function upgrade_6_2_0(): void {
2085 + $this->fix_notification_recipients_format();
2086 + $this->cancel_recipients_of_inactive_reports();
2087 + }
2088 +
2089 + /**
2090 + * Unsubscribe every recipient of each inactive report so their stored preferences match
2091 + * the reports' last saved on/off state.
2092 + *
2093 + * @return void
2094 + */
2095 + private function cancel_recipients_of_inactive_reports(): void {
2096 + $report_classes = array(
2097 + Tweak_Reminder::class,
2098 + Malware_Report::class,
2099 + Firewall_Report::class,
2100 + Audit_Report::class,
2101 + );
2102 +
2103 + foreach ( $report_classes as $report_class ) {
2104 + $model = wd_di()->get( $report_class );
2105 + if ( $model->check_active_status() ) {
2106 + continue;
2107 + }
2108 +
2109 + $changed = false;
2110 + foreach ( array( 'in_house_recipients', 'out_house_recipients' ) as $group ) {
2111 + $recipients = 'in_house_recipients' === $group
2112 + ? $model->in_house_recipients
2113 + : $model->out_house_recipients;
2114 + if ( ! is_array( $recipients ) ) {
2115 + continue;
2116 + }
2117 +
2118 + foreach ( $recipients as $key => $recipient ) {
2119 + if ( Notification::USER_SUBSCRIBE_CANCELED !== ( $recipient['status'] ?? '' ) ) {
2120 + $recipients[ $key ]['status'] = Notification::USER_SUBSCRIBE_CANCELED;
2121 + $changed = true;
2122 + }
2123 + }
2124 +
2125 + if ( 'in_house_recipients' === $group ) {
2126 + $model->in_house_recipients = $recipients;
2127 + } else {
2128 + $model->out_house_recipients = $recipients;
2129 + }
2130 + }
2131 +
2132 + if ( $changed ) {
2133 + $model->save();
2134 + }
2135 + }
2136 + }
2137 +
2138 + /**
2139 + * Heal recipients that older 6.x versions stored as email-keyed objects instead of arrays,
2140 + * which breaks Defender 5 on downgrade.
2141 + *
2142 + * @return void
2143 + */
2144 + private function fix_notification_recipients_format(): void {
2145 + $tables = array(
2146 + wd_di()->get( Audit_Report::class )->get_table(),
2147 + wd_di()->get( Firewall_Notification::class )->get_table(),
2148 + wd_di()->get( Firewall_Report::class )->get_table(),
2149 + wd_di()->get( Malware_Notification::class )->get_table(),
2150 + wd_di()->get( Malware_Report::class )->get_table(),
2151 + wd_di()->get( Tweak_Reminder::class )->get_table(),
2152 + );
2153 +
2154 + foreach ( $tables as $table ) {
2155 + $raw = get_site_option( $table );
2156 + if ( false === $raw ) {
2157 + continue;
2158 + }
2159 +
2160 + $data = is_array( $raw ) ? $raw : json_decode( $raw, true );
2161 + if ( ! is_array( $data ) ) {
2162 + continue;
2163 + }
2164 +
2165 + $changed = false;
2166 + foreach ( array( 'in_house_recipients', 'out_house_recipients', 'all_subscribers' ) as $key ) {
2167 + if ( isset( $data[ $key ] ) && is_array( $data[ $key ] ) ) {
2168 + // array_values() leaves a proper list untouched but reindexes an email-keyed one.
2169 + $reindexed = array_values( $data[ $key ] );
2170 + if ( $reindexed !== $data[ $key ] ) {
2171 + $data[ $key ] = $reindexed;
2172 + $changed = true;
2173 + }
2174 + }
2175 + }
2176 +
2177 + if ( $changed ) {
2178 + update_site_option( $table, wp_json_encode( $data ) );
2179 + }
2180 + }
2181 + }
2182 +
2183 + /**
2184 + * Sync all report schedules to match Malware Report.
2185 + *
2186 + * Since 6.0.0 the UI enforces a single shared schedule for all reports via sync_report_schedule.
2187 + * Sites upgrading from older versions may have each report on a different schedule.
2188 + * This migration copies Malware_Report's schedule to Scan_Settings (the UI master) and to
2189 + * all four report models, then recomputes est_timestamp on each so all report emails
2190 + * arrive at the same time.
2191 + *
2192 + * @return void
2193 + */
2194 + private function sync_all_report_schedules(): void {
2195 + // Get the schedule from Malware_Report.
2196 + $malware_report = wd_di()->get( Malware_Report::class );
2197 + $frequency = $malware_report->frequency;
2198 + $day = $malware_report->day;
2199 + $day_n = (int) $malware_report->day_n;
2200 + $time = $malware_report->time;
2201 +
2202 + // Update Scan_Settings so the schedule pill shows the correct label.
2203 + $scan_settings = wd_di()->get( Scan_Settings::class );
2204 + $scan_settings->frequency = $frequency;
2205 + $scan_settings->day = $day;
2206 + $scan_settings->day_n = $day_n;
2207 + $scan_settings->time = $time;
2208 + $scan_settings->save();
2209 +
2210 + // Apply the same schedule to all report models and recompute est_timestamp
2211 + // so every report fires at the same next occurrence.
2212 + $report_models = array(
2213 + $malware_report,
2214 + wd_di()->get( Firewall_Report::class ),
2215 + wd_di()->get( Audit_Report::class ),
2216 + wd_di()->get( Tweak_Reminder::class ),
2217 + );
2218 +
2219 + foreach ( $report_models as $model ) {
2220 + $model->frequency = $frequency;
2221 + $model->day = $day;
2222 + $model->day_n = $day_n;
2223 + $model->time = $time;
2224 +
2225 + $next_run = $model->get_next_run();
2226 + if ( $next_run instanceof \DateTime ) {
2227 + $model->est_timestamp = $next_run->getTimestamp();
2228 + }
2229 +
2230 + $model->save();
2231 + }
2232 +
2233 + // Show a one-time dashboard bubble so users know about the shared schedule.
2234 + update_site_option( \WP_Defender\Controller\Dashboard::REPORT_SCHEDULE_NOTICE_OPTION, true );
2235 + }
2236 +
2237 + /**
2238 + * Heal 'Scheduled Scanning' left disabled while its related report stayed active.
2239 + *
2240 + * The bundled "Basic Config" preset stored 'scheduled_scanning' out of sync with its
2241 + * 'report' flag, so applying it left scans disabled. Corrects the live setting and any
2242 + * already-stored config presets with the same mismatch.
2243 + *
2244 + * @return void
2245 + */
2246 + private function fix_scheduled_scanning_left_disabled(): void {
2247 + $scan_settings = wd_di()->get( Scan_Settings::class );
2248 + $malware_report = wd_di()->get( Malware_Report::class );
2249 + if ( ! $scan_settings->scheduled_scanning && Notification::STATUS_ACTIVE === $malware_report->status ) {
2250 + $scan_settings->scheduled_scanning = true;
2251 + $scan_settings->save();
2252 + }
2253 +
2254 + $service = wd_di()->get( Backup_Settings::class );
2255 + $configs = Config_Hub_Helper::get_configs( $service );
2256 +
2257 + foreach ( $configs as $key => $config ) {
2258 + $scan = $config['configs']['scan'] ?? null;
2259 + if ( ! is_array( $scan ) || ! isset( $scan['report'], $scan['scheduled_scanning'] ) ) {
2260 + continue;
2261 + }
2262 +
2263 + if ( 'enabled' === $scan['report'] && false === $scan['scheduled_scanning'] ) {
2264 + $configs[ $key ]['configs']['scan']['scheduled_scanning'] = true;
2265 + update_site_option( $key, $configs[ $key ] );
2266 + Config_Hub_Helper::update_on_hub( $configs[ $key ] );
2267 + }
2268 + }
2269 +
2270 + delete_site_transient( Config_Hub_Helper::CONFIGS_TRANSIENT_KEY );
2271 + }
2272 +
2273 + /**
2057 2274 * Clear force_auth_roles when user_roles is empty.
2058 2275 *
2059 2276 * @return void
2060 2277 */
@@ -2086,11 +2303,13 @@
2086 2303 return;
2087 2304 }
2088 2305
2089 2306 $model->type = 'report';
2090 - if ( isset( $model->configs['reminder'] ) && in_array( $model->configs['reminder'], array( 'daily', 'weekly', 'monthly' ), true ) ) {
2091 - $model->frequency = $model->configs['reminder'];
2092 - }
2307 +
2308 + $valid_frequencies = array( 'daily', 'weekly', 'monthly' );
2309 + $reminder = $model->configs['reminder'] ?? '';
2310 + $model->frequency = in_array( $reminder, $valid_frequencies, true ) ? $reminder : 'weekly';
2311 +
2093 2312 if ( null === $model->day || '' === $model->day ) {
2094 2313 $model->day = 'sunday';
2095 2314 }
2096 2315 if ( null === $model->time || '' === $model->time ) {
@@ -2095,8 +2314,18 @@
2095 2314 }
2096 2315 if ( null === $model->time || '' === $model->time ) {
2097 2316 $model->time = '4:00';
2098 2317 }
2318 + if ( ! is_int( $model->day_n ) || $model->day_n < 1 ) {
2319 + $model->day_n = 1;
2320 + }
2321 + // Compute est_timestamp.
2322 + $next_run = $model->get_next_run();
2323 + if ( $next_run instanceof \DateTime ) {
2324 + $model->est_timestamp = $next_run->getTimestamp();
2325 + }
2326 + // Clear the old notification style configs key.
2327 + unset( $model->configs['reminder'] );
2099 2328 $model->save();
2100 2329 }
2101 2330
2102 2331 /**