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

upgrader.php in Defender Security – Malware Scanner, Login Security & Firewall 3.0.1, at src/upgrader.php

967 lines 30.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WP_Defender;
4
5 use WP_Defender\Component\Feature_Modal;
6 use WP_Defender\Model\Scan as Model_Scan;
7 use WP_Defender\Model\Scan_Item;
8 use WP_Defender\Model\Setting\Security_Headers;
9 use WP_Defender\Model\Setting\Scan as Scan_Settings;
10 use WP_Defender\Model\Setting\Two_Fa as Two_Fa_Settings;
11 use WP_Defender\Component\Two_Fa as Two_Fa_Component;
12 use WP_Defender\Component\Config\Config_Hub_Helper;
13 use WP_Defender\Controller\Security_Tweaks;
14 use WP_Defender\Component\Legacy_Versions;
15 use WP_Defender\Component\Backup_Settings;
16 use WP_Defender\Model\Notification\Malware_Report;
17 use WP_Defender\Model\Notification\Audit_Report;
18 use WP_Defender\Model\Notification\Firewall_Notification;
19 use WP_Defender\Model\Notification\Firewall_Report;
20 use WP_Defender\Model\Notification\Malware_Notification;
21 use WP_Defender\Model\Notification\Tweak_Reminder;
22 use WP_Defender\Traits\User;
23
24 class Upgrader {
25 use User;
26
27 /**
28 * Migrate old security headers from security tweaks. Trigger it once time.
29 */
30 public function migrate_security_headers() {
31 $model = wd_di()->get( Security_Headers::class );
32 $new_key = $model->table;
33 $option = get_site_option( $new_key );
34
35 if ( empty( $option ) ) {
36 // Part of Security tweaks data.
37 $old_key = 'wd_hardener_settings';
38 $old_settings = get_site_option( $old_key );
39 if ( ! is_array( $old_settings ) ) {
40 $old_settings = json_decode( $old_settings, true );
41 if ( is_array( $old_settings ) && isset( $old_settings['data'] ) && ! empty( $old_settings['data'] ) ) {
42 // Exist 'X-Frame-Options'.
43 if ( isset( $old_settings['data']['sh_xframe'] ) && ! empty( $old_settings['data']['sh_xframe'] ) ) {
44 $header_data = $old_settings['data']['sh_xframe'];
45
46 $mode = ( isset( $header_data['mode'] ) && ! empty( $header_data['mode'] ) )
47 ? strtolower( $header_data['mode'] )
48 : false;
49 /**
50 * Directive ALLOW-FROM is deprecated. If header directive is ALLOW-FROM then set 'sameorigin'.
51 *
52 * @since 2.5.0
53 */
54 if ( 'allow-from' === $mode ) {
55 $model->sh_xframe_mode = 'sameorigin';
56 } elseif ( in_array( $mode, array( 'sameorigin', 'deny' ), true ) ) {
57 $model->sh_xframe_mode = $mode;
58 }
59 $model->sh_xframe = true;
60 }
61
62 // Exist 'X-XSS-Protection'.
63 if ( isset( $old_settings['data']['sh_xss_protection'] ) && ! empty( $old_settings['data']['sh_xss_protection'] ) ) {
64 $header_data = $old_settings['data']['sh_xss_protection'];
65
66 if ( isset( $header_data['mode'] )
67 && ! empty( $header_data['mode'] )
68 && in_array( $header_data['mode'], array( 'sanitize', 'block' ), true )
69 ) {
70 $model->sh_xss_protection_mode = $header_data['mode'];
71 $model->sh_xss_protection = true;
72 }
73 }
74
75 // Exist 'X-Content-Type-Options'.
76 if ( isset( $old_settings['data']['sh_content_type_options'] ) && ! empty( $old_settings['data']['sh_content_type_options'] ) ) {
77 $header_data = $old_settings['data']['sh_content_type_options'];
78
79 if ( isset( $header_data['mode'] ) && ! empty( $header_data['mode'] ) ) {
80 $model->sh_content_type_options_mode = $header_data['mode'];
81 $model->sh_content_type_options = true;
82 }
83 }
84
85 // Exist 'Strict Transport'.
86 if ( isset( $old_settings['data']['sh_strict_transport'] ) && ! empty( $old_settings['data']['sh_strict_transport'] ) ) {
87 $header_data = $old_settings['data']['sh_strict_transport'];
88
89 if ( isset( $header_data['hsts_preload'] ) && ! empty( $header_data['hsts_preload'] ) ) {
90 $model->hsts_preload = (int) $header_data['hsts_preload'];
91 }
92 if ( isset( $header_data['include_subdomain'] ) && ! empty( $header_data['include_subdomain'] ) ) {
93 $model->include_subdomain = in_array(
94 $header_data['include_subdomain'],
95 array( 'true', '1', 1 ),
96 true
97 ) ? 1 : 0;
98 }
99 if ( isset( $header_data['hsts_cache_duration'] ) && ! empty( $header_data['hsts_cache_duration'] ) ) {
100 $model->hsts_cache_duration = $header_data['hsts_cache_duration'];
101 }
102 $model->sh_strict_transport = true;
103 }
104
105 // Exist 'Referrer Policy'.
106 if ( isset( $old_settings['data']['sh_referrer_policy'] ) && ! empty( $old_settings['data']['sh_referrer_policy'] ) ) {
107 $header_data = $old_settings['data']['sh_referrer_policy'];
108
109 if ( isset( $header_data['mode'] ) && ! empty( $header_data['mode'] ) ) {
110 $model->sh_referrer_policy_mode = $header_data['mode'];
111 $model->sh_referrer_policy = true;
112 }
113 }
114
115 // Exist 'Feature-Policy'.
116 if ( isset( $old_settings['data']['sh_feature_policy'] ) && ! empty( $old_settings['data']['sh_feature_policy'] ) ) {
117 $header_data = $old_settings['data']['sh_feature_policy'];
118
119 if ( isset( $header_data['mode'] ) && ! empty( $header_data['mode'] ) ) {
120 $mode = strtolower( $header_data['mode'] );
121 $model->sh_feature_policy_mode = $mode;
122 if ( 'origins' === $mode && isset( $header_data['values'] ) && ! empty( $header_data['values'] ) ) {
123 // The values differ from the values of the 'X-Frame-Options' key, because they may be an array.
124 if ( is_array( $header_data['values'] ) ) {
125 $model->sh_feature_policy_urls = implode( PHP_EOL, $header_data['values'] );
126 // Otherwise.
127 } elseif ( is_string( $header_data['values'] ) ) {
128 $urls = explode( ' ', $header_data['values'] );
129 $model->sh_feature_policy_urls = implode( PHP_EOL, $urls );
130 }
131 }
132 $model->sh_feature_policy = true;
133 }
134 }
135 // Save.
136 $model->save();
137 }
138 }
139 }
140 }
141
142 /**
143 *
144 * If user upgrade from an older version to the latest version.
145 *
146 * @param $current_version
147 */
148 public function maybe_show_new_features( $current_version ) {
149 if ( false === $current_version ) {
150 // Do nothing.
151 return;
152 }
153
154 // Set the version where we have added the new feature.
155 // Update it when you want to show modal on a specific version.
156 if ( version_compare( $current_version, '2.4', '<' ) ) {
157 update_site_option( 'wd_show_new_feature', true );
158 }
159 }
160
161 /**
162 *
163 * Migrate configs for latest versions.
164 *
165 * @since 2.4
166 *
167 * @param $current_version
168 */
169 public function migrate_configs( $current_version ) {
170 if (
171 version_compare( $current_version, '2.2', '>=' )
172 && version_compare( $current_version, '2.4', '<' )
173 ) {
174 $config_component = wd_di()->get( Backup_Settings::class );
175 $prev_data = $config_component->backup_data();
176 if ( empty( $prev_data ) ) {
177 return;
178 }
179 $adapter = wd_di()->get( \WP_Defender\Component\Config\Config_Adapter::class );
180 $migrated_data = $adapter->upgrade( $prev_data );
181 $config_component->restore_data( $migrated_data, 'migration' );
182 // Hide Onboard page.
183 update_site_option( 'wp_defender_shown_activator', true );
184
185 $configs = $config_component->get_configs();
186 if ( ! empty( $configs ) ) {
187 foreach ( $configs as $k => $config ) {
188 if (
189 $config_component->verify_config_data( $config )
190 && ! $config_component->check_for_new_structure( $config['configs'] )
191 ) {
192 $new_data = $config;
193 $new_data['configs'] = $adapter->upgrade( $config['configs'] );
194
195 /**
196 * Import config 'strings' and the active tag if a config has it.
197 */
198 if ( isset( $config['is_active'] ) ) {
199 $new_data['is_active'] = $config['is_active'];
200 }
201 $new_data['strings'] = $config_component->import_module_strings( $new_data );
202 // Update config data.
203 update_site_option( $k, $new_data );
204 }
205 }
206 }
207 }
208 // For older versions we do not use old models, e.g. for version < 2.2. So the default values will be used.
209 }
210
211 private function upgrade_2_4_2() {
212 // Update Scan settings.
213 $model_settings = wd_di()->get( Scan_Settings::class );
214 $key = $model_settings->table;
215 $option = get_site_option( $key );
216 if ( ! empty( $option ) && ! is_array( $option ) ) {
217 $old_settings = json_decode( $option, true );
218 if ( is_array( $old_settings ) && isset( $old_settings['max_filesize'] ) ) {
219 $model_settings->filesize = (int) $old_settings['max_filesize'];
220 $model_settings->save();
221 }
222 }
223 // Update 'reminder_duration' value inside the Security Key tweak.
224 $old_settings = get_site_option( 'wd_hardener_settings' );
225 if ( ! empty( $old_settings ) && ! is_array( $old_settings ) ) {
226 $old_settings = json_decode( $old_settings, true );
227 $tweak_sec_key = wd_di()->get( \WP_Defender\Component\Security_Tweaks\Security_Key::class );
228
229 if ( is_array( $old_settings ) && isset( $old_settings['data']['securityReminderDuration'] )
230 && in_array( $old_settings['data']['securityReminderDuration'], $tweak_sec_key->reminder_frequencies(), true )
231 ) {
232 $tweak_sec_key->update_option( 'reminder_duration', $old_settings['data']['securityReminderDuration'] );
233 }
234 }
235 }
236
237 /**
238 *
239 * Migrate value of scan setting from 'integrity_check' to 'check_core'.
240 *
241 * @since 2.4.7
242 * @return void
243 */
244 private function migrate_scan_integrity_check() {
245 $model = new Scan_Settings();
246 $model->check_core = (bool) $model->integrity_check;
247 $model->save();
248 }
249
250 /**
251 * Run an upgrade/installation.
252 *
253 * @return void
254 */
255 public function run() {
256 // Sometimes multiple requests come at the same time. So we will only count the web requests.
257 if ( defined( 'DOING_AJAX' ) || defined( 'DOING_CRON' ) ) {
258 return;
259 }
260
261 $db_version = get_site_option( 'wd_db_version' );
262 if ( empty( $db_version ) ) {
263 update_site_option( 'wd_db_version', DEFENDER_DB_VERSION );
264 return;
265 }
266
267 if ( DEFENDER_DB_VERSION === $db_version ) {
268 return;
269 }
270
271 $this->maybe_show_new_features( $db_version );
272 $this->migrate_configs( $db_version );
273
274 if ( version_compare( $db_version, '2.2.9', '<' ) ) {
275 $this->migrate_security_headers();
276 }
277 if ( version_compare( $db_version, '2.4.2', '<' ) ) {
278 $this->upgrade_2_4_2();
279 }
280 if ( version_compare( $db_version, '2.4.7', '<' ) ) {
281 $this->index_database();
282 $this->migrate_scan_integrity_check();
283 }
284 if ( version_compare( $db_version, '2.4.10', '<' ) ) {
285 $this->upgrade_2_4_10();
286 }
287 if ( version_compare( $db_version, '2.5.0', '<' ) ) {
288 $this->upgrade_2_5_0();
289 }
290 if ( version_compare( $db_version, '2.5.2', '<' ) ) {
291 $this->upgrade_2_5_2();
292 }
293 if ( version_compare( $db_version, '2.5.4', '<' ) ) {
294 $this->upgrade_2_5_4();
295 }
296 if ( version_compare( $db_version, '2.5.6', '<' ) ) {
297 $this->upgrade_2_5_6();
298 }
299 if ( version_compare( $db_version, '2.6.0', '<' ) ) {
300 $this->upgrade_2_6_0();
301 }
302 if ( version_compare( $db_version, '2.6.1', '<' ) ) {
303 $this->upgrade_2_6_1();
304 }
305 if ( version_compare( $db_version, '2.6.2', '<' ) ) {
306 $this->upgrade_2_6_2();
307 }
308 if ( version_compare( $db_version, '2.7.0', '<' ) ) {
309 $this->upgrade_2_7_0();
310 }
311 if ( version_compare( $db_version, '2.8.0', '<' ) ) {
312 $this->upgrade_2_8_0();
313 }
314 if ( version_compare( $db_version, '2.8.3', '<' ) ) {
315 $this->upgrade_2_8_3();
316 }
317 if ( version_compare( $db_version, '3.0.0', '<' ) ) {
318 $this->upgrade_3_0_0();
319 }
320
321 defender_no_fresh_install();
322 // Don't run any function below this line.
323 update_site_option( 'wd_db_version', DEFENDER_DB_VERSION );
324 }
325
326 /**
327 * Index necessary columns.
328 * Sometimes this function call twice that's why we have to check index already exists or not.
329 * `dbDelta` not work on `ALTER TABLE` query, so we had to use $wpdb->query().
330 *
331 * @since 2.4.7
332 * @return void
333 */
334 private function index_database() {
335 global $wpdb;
336 $wpdb->hide_errors();
337
338 $this->add_index_to_defender_email_log( $wpdb );
339 $this->add_index_to_defender_audit_log( $wpdb );
340 $this->add_index_to_defender_scan_item( $wpdb );
341 $this->add_index_to_defender_lockout_log( $wpdb );
342 $this->add_index_to_defender_lockout( $wpdb );
343 }
344
345 /**
346 * Add index to defender_email_log.
347 *
348 * @param $wpdb
349 * @since 2.4.7
350 */
351 private function add_index_to_defender_email_log( $wpdb ) {
352 $table = $wpdb->base_prefix . 'defender_email_log';
353 // Check index already exists or not.
354 $result = $wpdb->get_row( "SHOW INDEX FROM {$table} WHERE Key_name = 'source';", ARRAY_A );
355
356 if ( is_array( $result ) ) {
357 return;
358 }
359
360 $sql = "ALTER TABLE {$table} ADD INDEX `source` (`source`);";
361 $wpdb->query( $sql );
362 }
363
364 /**
365 * Add index to defender_audit_log.
366 *
367 * @param $wpdb
368 * @since 2.4.7
369 */
370 private function add_index_to_defender_audit_log( $wpdb ) {
371 $table = $wpdb->base_prefix . 'defender_audit_log';
372 // Check index already exists or not.
373 $result = $wpdb->get_row( "SHOW INDEX FROM {$table} WHERE Key_name = 'event_type';", ARRAY_A );
374
375 if ( is_array( $result ) ) {
376 return;
377 }
378
379 $sql = "ALTER TABLE {$table}
380 ADD INDEX `event_type` (`event_type`),
381 ADD INDEX `action_type` (`action_type`),
382 ADD INDEX `user_id` (`user_id`),
383 ADD INDEX `context` (`context`),
384 ADD INDEX `ip` (`ip`);";
385 $wpdb->query( $sql );
386 }
387
388 /**
389 * Add index to defender_scan_item.
390 *
391 * @param $wpdb
392 * @since 2.4.7
393 */
394 private function add_index_to_defender_scan_item( $wpdb ) {
395 $table = $wpdb->base_prefix . 'defender_scan_item';
396 // Check index already exists or not.
397 $result = $wpdb->get_row( "SHOW INDEX FROM {$table} WHERE Key_name = 'type';", ARRAY_A );
398
399 if ( is_array( $result ) ) {
400 return;
401 }
402
403 $sql = "ALTER TABLE {$table} ADD INDEX `type` (`type`), ADD INDEX `status` (`status`);";
404 $wpdb->query( $sql );
405 }
406
407 /**
408 * Add index to defender_lockout_log.
409 *
410 * @param $wpdb
411 * @since 2.4.7
412 */
413 private function add_index_to_defender_lockout_log( $wpdb ) {
414 $table = $wpdb->base_prefix . 'defender_lockout_log';
415 // Check index already exists or not.
416 $result = $wpdb->get_row( "SHOW INDEX FROM {$table} WHERE Key_name = 'ip';", ARRAY_A );
417
418 if ( is_array( $result ) ) {
419 return;
420 }
421
422 $sql = "ALTER TABLE {$table} ADD INDEX `ip` (`ip`), ADD INDEX `type` (`type`), ADD INDEX `tried` (`tried`);";
423 $wpdb->query( $sql );
424 }
425
426 /**
427 * Add index to defender_lockout.
428 *
429 * @param $wpdb
430 * @since 2.4.7
431 */
432 private function add_index_to_defender_lockout( $wpdb ) {
433 $table = $wpdb->base_prefix . 'defender_lockout';
434 // Check index already exists or not.
435 $result = $wpdb->get_row( "SHOW INDEX FROM {$table} WHERE Key_name = 'ip';", ARRAY_A );
436
437 if ( is_array( $result ) ) {
438 return;
439 }
440
441 $sql = "ALTER TABLE {$table}
442 ADD INDEX `ip` (`ip`),
443 ADD INDEX `status` (`status`),
444 ADD INDEX `attempt` (`attempt`),
445 ADD INDEX `attempt_404` (`attempt_404`);";
446 $wpdb->query( $sql );
447 }
448
449 /**
450 * Upgrade to 2.4.10.
451 *
452 * @since 2.4.10
453 */
454 private function upgrade_2_4_10() {
455 $service = wd_di()->get( Backup_Settings::class );
456 $configs = Config_Hub_Helper::get_configs( $service );
457 $deprecated_keys = array(
458 // Reason: updated or removed some tweak slugs.
459 'security_key',
460 'wp-rest-api',
461 // Reason: moved the security headers to a separate module.
462 'sh-referrer-policy',
463 'sh-strict-transport',
464 'sh-xframe',
465 'sh-content-security',
466 'sh-content-type-options',
467 'sh-feature-policy',
468 'sh-xss-protection',
469 );
470 foreach ( $configs as $key => $config ) {
471 $is_updated = false;
472 // Remove deprecated 'data' key inside Security tweaks.
473 if ( isset( $config['configs']['security_tweaks']['data'] ) ) {
474 unset( $configs[ $key ]['configs']['security_tweaks']['data'] );
475 $is_updated = true;
476 }
477 // Remove deprecated keys in 'issues'.
478 if ( isset( $config['configs']['security_tweaks']['issues'] ) ) {
479 foreach ( $config['configs']['security_tweaks']['issues'] as $iss_key => $issue ) {
480 if ( in_array( $issue, $deprecated_keys, true ) ) {
481 unset( $configs[ $key ]['configs']['security_tweaks']['issues'][ $iss_key ] );
482 $is_updated = true;
483 }
484 }
485 }
486 // In 'ignore'.
487 if ( isset( $config['configs']['security_tweaks']['ignore'] ) ) {
488 foreach ( $config['configs']['security_tweaks']['ignore'] as $ign_key => $issue ) {
489 if ( in_array( $issue, $deprecated_keys, true ) ) {
490 unset( $configs[ $key ]['configs']['security_tweaks']['ignore'][ $ign_key ] );
491 $is_updated = true;
492 }
493 }
494 }
495 // In 'fixed'.
496 if ( isset( $config['configs']['security_tweaks']['fixed'] ) ) {
497 foreach ( $config['configs']['security_tweaks']['fixed'] as $fix_key => $issue ) {
498 if ( in_array( $issue, $deprecated_keys, true ) ) {
499 unset( $configs[ $key ]['configs']['security_tweaks']['fixed'][ $fix_key ] );
500 $is_updated = true;
501 }
502 }
503 }
504
505 if ( $is_updated ) {
506 update_site_option( $key, $configs[ $key ] );
507 Config_Hub_Helper::update_on_hub( $configs[ $key ] );
508 if ( isset( $config['is_active'] ) ) {
509 $model = new \WP_Defender\Model\Setting\Security_Tweaks();
510 $model->issues = $configs[ $key ]['configs']['security_tweaks']['issues'];
511 $model->ignore = $configs[ $key ]['configs']['security_tweaks']['ignore'];
512 $model->fixed = $configs[ $key ]['configs']['security_tweaks']['fixed'];
513 $model->save();
514 }
515 }
516 }
517 }
518
519 /**
520 * Upgrade to 2.5.0.
521 *
522 * @since 2.5.0
523 */
524 private function upgrade_2_5_0() {
525 $model = wd_di()->get( Security_Headers::class );
526 // Directive ALLOW-FROM is deprecated. If header directive is ALLOW-FROM then set 'sameorigin'.
527 if ( isset( $model->sh_xframe_mode ) && 'allow-from' === $model->sh_xframe_mode ) {
528 $model->sh_xframe_mode = 'sameorigin';
529 $model->save();
530 }
531 // Display a new feature about Pwned Passwords on Welcome modal.
532 update_site_option( 'wd_show_feature_password_pwned', true );
533 /**
534 * Uncheck 'File change detection' option if there was checked only child 'Scan theme files' option and save
535 * settings. Also remove items for 'Scan theme files' without run Scan.
536 */
537 // Step#1.
538 $scan_settings = new Scan_Settings();
539 if (
540 $scan_settings->integrity_check
541 && ! $scan_settings->check_core
542 && ! $scan_settings->check_plugins
543 ) {
544 $scan_settings->integrity_check = false;
545 $scan_settings->save();
546 }
547 // Step#2.
548 $scan_model = Model_Scan::get_active();
549 if ( is_object( $scan_model ) ) {
550 // Nothing changes.
551 return;
552 }
553 $scan_model = Model_Scan::get_last();
554 if ( is_object( $scan_model ) && ! is_wp_error( $scan_model ) ) {
555 // Active items.
556 $items = $scan_model->get_issues( Scan_Item::TYPE_THEME_CHECK, Scan_Item::STATUS_ACTIVE );
557 foreach ( $items as $item ) {
558 $scan_model->remove_issue( $item->id );
559 }
560 // Ignored items.
561 $items = $scan_model->get_issues( Scan_Item::TYPE_THEME_CHECK, Scan_Item::STATUS_IGNORE );
562 foreach ( $items as $item ) {
563 $scan_model->remove_issue( $item->id );
564 }
565 }
566 }
567 /**
568 * Upgrade. Display a new feature about Reset Password on Welcome modal.
569 *
570 * @since 2.5.2
571 */
572 private function upgrade_2_5_2() {
573 update_site_option( 'wd_show_feature_password_reset', true );
574 }
575
576 /**
577 * Upgrade. Display a new feature about Google Recaptcha on Welcome modal.
578 *
579 * @since 2.5.4
580 */
581 private function upgrade_2_5_4() {
582 update_site_option( 'wd_show_feature_google_recaptcha', true );
583 }
584
585 private function force_nf_lockout_exclusions() {
586 $nf_settings = new \WP_Defender\Model\Setting\Notfound_Lockout();
587 $allowlist = $nf_settings->get_lockout_list( 'allowlist' );
588 $default_allowlist = array( '.css', '.js', '.map' );
589 $is_save = false;
590 if ( ! empty( $allowlist ) ) {
591 foreach ( $default_allowlist as $item ) {
592 if ( ! in_array( $item, $allowlist ) ) {
593 $allowlist[] = $item;
594 $is_save = true;
595 }
596 }
597 $nf_settings->whitelist = implode( "\n", $allowlist );
598 } else {
599 $nf_settings->whitelist = ".css\n.js\n.map";
600 $is_save = true;
601 }
602 // Save it.
603 if ( $is_save ) {
604 $nf_settings->save();
605 }
606 }
607
608 /**
609 * Upgrade to 2.5.6.
610 *
611 * @since 2.5.6
612 */
613 private function upgrade_2_5_6() {
614 $adapted_component = wd_di()->get( Legacy_Versions::class );
615 $issue_list = $adapted_component->get_scan_issue_data();
616 $ignored_list = $adapted_component->get_scan_ignored_data();
617 if ( ! empty( $issue_list ) || ! empty( $ignored_list ) ) {
618 $adapted_component->migrate_scan_data( $issue_list, $ignored_list );
619 $adapted_component->remove_old_scan_data( $issue_list, $ignored_list );
620 $adapted_component->change_onboarding_status();
621 }
622
623 // Trigger recurring cron schedule for autogenerate security salt.
624 $security_tweaks = wd_di()->get( Security_Tweaks::class );
625 $security_tweaks->get_security_key()->cron_schedule();
626 // Add some lockout extension to old installations forced.
627 $this->force_nf_lockout_exclusions();
628 // Display a new feature on Welcome modal.
629 update_site_option( 'wd_show_feature_file_extensions', true );
630 }
631
632 /**
633 * Upgrade to 2.6.0.
634 *
635 * @since 2.6.0
636 */
637 private function upgrade_2_6_0() {
638 update_site_option( 'wd_show_feature_user_agent', true );
639 }
640
641 private function update_scan_error_send_body( $model ) {
642 if (
643 isset( $model->configs['template']['error']['body'] )
644 && ! empty( $model->configs['template']['error']['body'] )
645 ) {
646 $needle = '{follow this link} and check the logs to see what casued the failure';
647 if ( false !== stripos( $model->configs['template']['error']['body'], $needle ) ) {
648 $model->configs['template']['error']['body'] = str_replace(
649 $needle,
650 'visit your site and run a manual scan',
651 $model->configs['template']['error']['body']
652 );
653 $model->save();
654 }
655 }
656 }
657
658 /**
659 * Upgrade to 2.6.1.
660 *
661 * @since 2.6.1
662 */
663 private function upgrade_2_6_1() {
664 // Add the "What's new" modal.
665 update_site_option( 'wd_show_feature_woo_recaptcha', true );
666 // Update the title of the basic config.
667 $config_component = wd_di()->get( Backup_Settings::class );
668 $configs = $config_component->get_configs();
669 if ( ! empty( $configs ) ) {
670 foreach ( $configs as $k => $config ) {
671 if ( 0 === strcmp( $config['name'], __( 'Basic config', 'wpdef' ) ) ) {
672 $config['name'] = __( 'Basic Config', 'wpdef' );
673
674 update_site_option( $k, $config );
675 delete_site_transient( Config_Hub_Helper::CONFIGS_TRANSIENT_KEY );
676 break;
677 }
678 }
679 }
680 // Update scan emails for 'When failed to scan' option.
681 $malware_notification = wd_di()->get( \WP_Defender\Model\Notification\Malware_Notification::class );
682 $this->update_scan_error_send_body( $malware_notification );
683 $malware_report = wd_di()->get( Malware_Report::class );
684 $this->update_scan_error_send_body( $malware_report );
685 }
686
687 /**
688 * Upgrade to 2.6.2.
689 *
690 * @since 2.6.2
691 */
692 private function upgrade_2_6_2() {
693 // Add the "What's new" modal.
694 update_site_option( 'wd_show_feature_plugin_vulnerability', true );
695 // Add Black Friday notice.
696 update_site_option( 'wp_defender_show_black_friday', true );
697 }
698
699 /**
700 * Update 2FA email template.
701 */
702 private function update_2fa_send_body() {
703 $model = wd_di()->get( Two_Fa_Settings::class );
704 if ( isset( $model->email_body ) ) {
705 $model->email_body = 'Hi {{display_name}},
706
707 Your temporary password is {{passcode}}. To finish logging in, copy and paste the temporary password into the Password field on the login screen.';
708 $model->save();
709 }
710 }
711
712 /**
713 * Update Malware Scan email template.
714 *
715 * @since 2.7.0
716 * @return void
717 */
718 private function update_malware_scan_send_body() {
719 $models = array(
720 wd_di()->get( \WP_Defender\Model\Notification\Malware_Notification::class ),
721 wd_di()->get( \WP_Defender\Model\Notification\Malware_Report::class ),
722 );
723
724 foreach ( $models as $model ) {
725 $is_updated = false;
726 if ( ! empty( $model->configs['template']['error']['body'] ) ) {
727 $subject = $model->configs['template']['error']['body'];
728 $subject = $this->update_malware_scan_send_body_common( $subject );
729 $subject = str_replace( 'I couldn', 'We couldn', $subject );
730
731 if ( $model->configs['template']['error']['body'] !== $subject ) {
732 $is_updated = true;
733
734 $model->configs['template']['error']['body'] = $subject;
735 }
736 }
737
738 if ( ! empty( $model->configs['template']['found']['body'] ) ) {
739 $subject = $model->configs['template']['found']['body'];
740 $subject = $this->update_malware_scan_send_body_common( $subject );
741 $subject = preg_replace(
742 "/I(’|'|\\\')ve finished scanning \{SITE_URL\} for vulnerabilities and I found \{ISSUES_COUNT\} issues that you should take a closer look at!/i",
743 '{ISSUES_COUNT} vulnerabilities were identified for {SITE_URL} during a Malware Scan. See details for each issue below.',
744 $subject
745 );
746 $subject = preg_replace( '/(\R+){ISSUES_LIST}/i', "\n\n{ISSUES_LIST}", $subject );
747
748 if ( $model->configs['template']['found']['body'] !== $subject ) {
749 $is_updated = true;
750
751 $model->configs['template']['found']['body'] = $subject;
752 }
753 }
754
755 if ( ! empty( $model->configs['template']['not_found']['body'] ) ) {
756 $subject = $model->configs['template']['not_found']['body'];
757 $subject = $this->update_malware_scan_send_body_common( $subject );
758 $subject = preg_replace(
759 "/I(’|'|\\\')ve finished scanning \{SITE_URL\} for vulnerabilities and I found nothing\. Well done for running such a tight ship!/i",
760 'No vulnerabilities have been found for {SITE_URL}.',
761 $subject
762 );
763 $subject = preg_replace(
764 "/(\R+)Keep up the good work! With regular security scans and a well-hardened installation you\'ll be just fine\./i",
765 '',
766 $subject
767 );
768
769 if ( $model->configs['template']['not_found']['body'] !== $subject ) {
770 $is_updated = true;
771
772 $model->configs['template']['not_found']['body'] = $subject;
773 }
774 }
775
776 if ( true === $is_updated ) {
777 $model->save();
778 }
779 }
780 }
781
782 /**
783 * Update Malware Scan email template common parts.
784 *
785 * @param string $subject
786 *
787 * @since 2.7.0
788 * @return string
789 */
790 private function update_malware_scan_send_body_common( $subject ) {
791 $subject = preg_replace( '/(\R+)WP Defender here, reporting back from the front\./i', '', $subject );
792 return preg_replace( '/(\R+)Stay Safe,(\R+)WP Defender(\R+)WPMU DEV Superhero$/i', '', $subject );
793 }
794
795 /**
796 * Upgrade to 2.7.0.
797 *
798 * @since 2.7.0
799 */
800 private function upgrade_2_7_0() {
801 $malware_report = wd_di()->get( Malware_Report::class );
802 $scan_settings = wd_di()->get( Scan_Settings::class );
803 // Migrate data from Malware_Report to Scan settings.
804 $scan_settings->scheduled_scanning = \WP_Defender\Model\Notification::STATUS_ACTIVE === $malware_report->status;
805 $scan_settings->frequency = $malware_report->frequency;
806 $scan_settings->day = $malware_report->day;
807 $scan_settings->day_n = $malware_report->day_n;
808 $scan_settings->time = $malware_report->time;
809 $scan_settings->save();
810
811 // Remove Black Friday notice.
812 delete_site_option( 'wp_defender_show_black_friday' );
813 // Add the "What's new" modal.
814 update_site_option( Feature_Modal::FEATURE_SLUG, true );
815 $this->update_2fa_send_body();
816 $this->update_malware_scan_send_body();
817 }
818
819 /**
820 * Update in_house recipients empty role.
821 *
822 * @since 2.8.0
823 * @return void
824 */
825 private function update_in_house_recipients_empty_role() {
826 $models = array(
827 wd_di()->get( Audit_Report::class ),
828 wd_di()->get( Firewall_Notification::class ),
829 wd_di()->get( Firewall_Report::class ),
830 wd_di()->get( Malware_Notification::class ),
831 wd_di()->get( Malware_Report::class ),
832 wd_di()->get( Tweak_Reminder::class ),
833 );
834
835 foreach ( $models as $model ) {
836 if ( ! empty( $model->in_house_recipients ) && is_array( $model->in_house_recipients ) ) {
837 $is_updated = false;
838
839 foreach ( $model->in_house_recipients as &$recipient ) {
840 if ( empty( $recipient['role'] ) ) {
841 $is_updated = true;
842 $recipient['role'] = $this->get_current_user_role( $recipient['id'] );
843 }
844 }
845
846 if ( true === $is_updated ) {
847 $model->save();
848 }
849 }
850 }
851 }
852
853 /**
854 * Delete temp geolite file from /wp-admin directory.
855 *
856 * @since 2.8.0
857 * @return void
858 */
859 private function delete_tmp_geolite_file() {
860 $pattern = ABSPATH . 'wp-admin/geolite2-country*.tar.gz';
861 array_map( 'unlink', glob( $pattern ) );
862 }
863
864 /**
865 * Migrate old keys to checked options.
866 *
867 * @since 2.8.0
868 * @return void
869 */
870 private function update_2fa_methods() {
871 $settings = wd_di()->get( Two_Fa_Settings::class );
872 if ( $settings->enabled ) {
873 $service = wd_di()->get( Two_Fa_Component::class );
874 $query = new \WP_User_Query(
875 array(
876 'blog_id' => 0,
877 'meta_key' => 'defenderAuthOn',
878 'meta_value' => true,
879 'fields' => 'ID',
880 )
881 );
882 if ( $query->get_total() > 0 ) {
883 // The data is independent of the user's data.
884 $providers = $service->get_providers();
885 $totp_slug = \WP_Defender\Component\Two_Factor\Providers\Totp::$slug;
886 // Add TOTP slug.
887 $enabled_providers = array( $totp_slug );
888 // If 'Enable lost phone' option is checked then we add the slug of the Email provider too.
889 if ( true === $settings->lost_phone ) {
890 $enabled_providers[] = \WP_Defender\Component\Two_Factor\Providers\Fallback_Email::$slug;
891 }
892 // Add slugs to the list of the enabled providers.
893 $enabled_providers = array_intersect( $enabled_providers, array_keys( $providers ) );
894 foreach ( $query->get_results() as $user_id ) {
895 // If 2FA module is configured for the current user
896 if ( $service->is_enabled_otp_for_user( $user_id ) ) {
897 update_user_meta( $user_id, Two_Fa_Component::ENABLED_PROVIDERS_USER_KEY, $enabled_providers );
898 // Set TOTP as the default auth provider.
899 update_user_meta( $user_id, Two_Fa_Component::DEFAULT_PROVIDER_USER_KEY, $totp_slug );
900 }
901 }
902 }
903 }
904 }
905
906 /**
907 * Upgrade to 2.8.0.
908 *
909 * @since 2.8.0
910 * @return void
911 */
912 private function upgrade_2_8_0() {
913 // Add the modal "What's new".
914 update_site_option( Feature_Modal::FEATURE_SLUG, true );
915 $this->update_in_house_recipients_empty_role();
916 $this->delete_tmp_geolite_file();
917 $this->update_2fa_methods();
918 }
919
920 /**
921 * Add column country_iso_code and index to table.
922 *
923 * @since 2.8.1
924 * @return void
925 */
926 private function add_country_iso_code_column() {
927 if ( ! function_exists( 'maybe_add_column' ) ) {
928 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
929 }
930
931 global $wpdb;
932
933 $table_name = $wpdb->base_prefix . 'defender_lockout_log';
934 $column_name = 'country_iso_code';
935 $create_ddl = "ALTER TABLE {$table_name} ADD {$column_name} CHAR(2) DEFAULT NULL";
936
937 maybe_add_column( $table_name, $column_name, $create_ddl );
938
939 $index_ddl = "CREATE INDEX {$column_name} ON {$table_name} ({$column_name})";
940
941 $prev_val = $wpdb->hide_errors();
942 $wpdb->query( $index_ddl );
943 $wpdb->show_errors( $prev_val );
944 }
945
946 /**
947 * Upgrade to 2.8.3.
948 *
949 * @since 2.8.3
950 * @return void
951 */
952 private function upgrade_2_8_3() {
953 $this->add_country_iso_code_column();
954 }
955
956 /**
957 * Upgrade to 3.0.0.
958 *
959 * @since 3.0.0
960 * @return void
961 */
962 private function upgrade_3_0_0() {
963 // Add the modal "What's new".
964 update_site_option( Feature_Modal::FEATURE_SLUG, true );
965 }
966 }
967