PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 3.1.0
Defender Security – Malware Scanner, Login Security & Firewall v3.1.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
defender-security / src / upgrader.php

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

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