PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.6.2
Defender Security – Malware Scanner, Login Security & Firewall v5.6.2
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 / class-upgrader.php

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

1,912 lines 59.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * This file contains the logic for updating the WP Defender plugin.
4 *
5 * @package WP_Defender
6 */
7
8 namespace WP_Defender;
9
10 use wpdb;
11 use WP_User_Query;
12 use WP_Filesystem_Base;
13 use WP_Defender\Traits\IO;
14 use WP_Defender\Traits\User;
15 use WP_Defender\Component\Crypt;
16 use WP_Defender\Component\Firewall;
17 use WP_Defender\Component\Webauthn;
18 use WP_Defender\Model\Notification;
19 use Safe\Exceptions\SodiumException;
20 use WP_Defender\Component\Feature_Modal;
21 use WP_Defender\Controller\Data_Tracking;
22 use WP_Defender\Component\Backup_Settings;
23 use WP_Defender\Traits\Defender_Bootstrap;
24 use WP_Defender\Component\Legacy_Versions;
25 use WP_Defender\Controller\Security_Tweaks;
26 use WP_Defender\Model\Setting\Security_Headers;
27 use WP_Defender\Model\Setting\Notfound_Lockout;
28 use WP_Defender\Model\Notification\Audit_Report;
29 use WP_Defender\Model\Setting\Global_Ip_Lockout;
30 use WP_Defender\Component\Config\Config_Adapter;
31 use WP_Defender\Model\Setting\User_Agent_Lockout;
32 use WP_Defender\Integrations\MaxMind_Geolocation;
33 use WP_Defender\Traits\Webauthn as Webauthn_Trait;
34 use WP_Defender\Model\Notification\Malware_Report;
35 use WP_Defender\Model\Notification\Tweak_Reminder;
36 use WP_Defender\Component\Config\Config_Hub_Helper;
37 use WP_Defender\Model\Notification\Firewall_Report;
38 use WP_Defender\Model\Setting\Scan as Scan_Settings;
39 use WP_Defender\Component\Two_Fa as Two_Fa_Component;
40 use WP_Defender\Component\Two_Factor\Providers\Totp;
41 use WP_Defender\Model\Setting\Two_Fa as Two_Fa_Settings;
42 use WP_Defender\Component\Security_Tweaks\Security_Key;
43 use WP_Defender\Model\Notification\Malware_Notification;
44 use WP_Defender\Model\Notification\Firewall_Notification;
45 use WP_Defender\Component\Two_Factor\Providers\Fallback_Email;
46 use WP_Defender\Helper\Analytics\Firewall as Firewall_Analytics;
47 use WP_Defender\Model\Setting\Blacklist_Lockout as Model_Blacklist_Lockout;
48 use WP_Defender\Model\Setting\Firewall as Model_Firewall;
49 use WP_Defender\Component\User_Agent as User_Agent_Service;
50 use function WP_Filesystem;
51
52 /**
53 * Upgrade the WP Defender plugin to the latest version.
54 */
55 class Upgrader {
56
57 use User;
58 use Webauthn_Trait;
59 use IO;
60 use Defender_Bootstrap;
61
62 /**
63 * Migrate old security headers from security tweaks. Trigger it once time.
64 *
65 * @return void
66 */
67 public function migrate_security_headers(): void {
68 $model = wd_di()->get( Security_Headers::class );
69 $option = get_site_option( $model->get_table() );
70
71 if ( empty( $option ) ) {
72 // Part of Security tweaks data.
73 $old_key = 'wd_hardener_settings';
74 $old_settings = get_site_option( $old_key );
75 if ( ! is_array( $old_settings ) ) {
76 $old_settings = json_decode( $old_settings, true );
77 if ( is_array( $old_settings ) && isset( $old_settings['data'] ) && ! empty( $old_settings['data'] ) ) {
78 // Exist 'X-Frame-Options'.
79 if ( isset( $old_settings['data']['sh_xframe'] ) && ! empty( $old_settings['data']['sh_xframe'] ) ) {
80 $header_data = $old_settings['data']['sh_xframe'];
81
82 $mode = ( isset( $header_data['mode'] ) && ! empty( $header_data['mode'] ) )
83 ? strtolower( $header_data['mode'] )
84 : false;
85 /**
86 * Directive ALLOW-FROM is deprecated. If header directive is ALLOW-FROM then set 'sameorigin'.
87 *
88 * @since 2.5.0
89 */
90 if ( 'allow-from' === $mode ) {
91 $model->sh_xframe_mode = 'sameorigin';
92 } elseif ( in_array( $mode, array( 'sameorigin', 'deny' ), true ) ) {
93 $model->sh_xframe_mode = $mode;
94 }
95 $model->sh_xframe = true;
96 }
97
98 // Exist 'X-XSS-Protection'.
99 if ( isset( $old_settings['data']['sh_xss_protection'] ) && ! empty( $old_settings['data']['sh_xss_protection'] ) ) {
100 $header_data = $old_settings['data']['sh_xss_protection'];
101
102 if ( isset( $header_data['mode'] )
103 && ! empty( $header_data['mode'] )
104 && in_array( $header_data['mode'], array( 'sanitize', 'block' ), true )
105 ) {
106 $model->sh_xss_protection_mode = $header_data['mode'];
107 $model->sh_xss_protection = true;
108 }
109 }
110
111 // Exist 'X-Content-Type-Options'.
112 if ( isset( $old_settings['data']['sh_content_type_options'] ) && ! empty( $old_settings['data']['sh_content_type_options'] ) ) {
113 $header_data = $old_settings['data']['sh_content_type_options'];
114
115 if ( isset( $header_data['mode'] ) && ! empty( $header_data['mode'] ) ) {
116 $model->sh_content_type_options_mode = $header_data['mode'];
117 $model->sh_content_type_options = true;
118 }
119 }
120
121 // Exist 'Strict Transport'.
122 if ( isset( $old_settings['data']['sh_strict_transport'] ) && ! empty( $old_settings['data']['sh_strict_transport'] ) ) {
123 $header_data = $old_settings['data']['sh_strict_transport'];
124
125 if ( isset( $header_data['hsts_preload'] ) && ! empty( $header_data['hsts_preload'] ) ) {
126 $model->hsts_preload = (int) $header_data['hsts_preload'];
127 }
128 if ( isset( $header_data['include_subdomain'] ) && ! empty( $header_data['include_subdomain'] ) ) {
129 $model->include_subdomain = in_array(
130 $header_data['include_subdomain'],
131 array( 'true', '1', 1 ),
132 true
133 ) ? 1 : 0;
134 }
135 if ( isset( $header_data['hsts_cache_duration'] ) && ! empty( $header_data['hsts_cache_duration'] ) ) {
136 $model->hsts_cache_duration = $header_data['hsts_cache_duration'];
137 }
138 $model->sh_strict_transport = true;
139 }
140
141 // Exist 'Referrer Policy'.
142 if ( isset( $old_settings['data']['sh_referrer_policy'] ) && ! empty( $old_settings['data']['sh_referrer_policy'] ) ) {
143 $header_data = $old_settings['data']['sh_referrer_policy'];
144
145 if ( isset( $header_data['mode'] ) && ! empty( $header_data['mode'] ) ) {
146 $model->sh_referrer_policy_mode = $header_data['mode'];
147 $model->sh_referrer_policy = true;
148 }
149 }
150
151 // Exist 'Feature-Policy'.
152 if ( isset( $old_settings['data']['sh_feature_policy'] ) && ! empty( $old_settings['data']['sh_feature_policy'] ) ) {
153 $header_data = $old_settings['data']['sh_feature_policy'];
154
155 if ( isset( $header_data['mode'] ) && ! empty( $header_data['mode'] ) ) {
156 $mode = strtolower( $header_data['mode'] );
157 $model->sh_feature_policy_mode = $mode;
158 if ( 'origins' === $mode && isset( $header_data['values'] ) && ! empty( $header_data['values'] ) ) {
159 // The values differ from the values of the 'X-Frame-Options' key, because they may be an array.
160 if ( is_array( $header_data['values'] ) ) {
161 $model->sh_feature_policy_urls = implode( PHP_EOL, $header_data['values'] );
162 // Otherwise.
163 } elseif ( is_string( $header_data['values'] ) ) {
164 $urls = explode( ' ', $header_data['values'] );
165 $model->sh_feature_policy_urls = implode( PHP_EOL, $urls );
166 }
167 }
168 $model->sh_feature_policy = true;
169 }
170 }
171 // Save.
172 $model->save();
173 }
174 }
175 }
176 }
177
178 /**
179 * If user upgrade from an older version to the latest version.
180 *
181 * @param mixed $current_version The current version.
182 *
183 * @return void
184 */
185 public function maybe_show_new_features( $current_version ) {
186 if ( false === $current_version ) {
187 // Do nothing.
188 return;
189 }
190
191 // Set the version where we have added the new feature.
192 // Update it when you want to show modal on a specific version.
193 if ( version_compare( $current_version, '2.4', '<' ) ) {
194 update_site_option( 'wd_show_new_feature', true );
195 }
196 }
197
198 /**
199 * Migrate configs for latest versions.
200 *
201 * @param mixed $current_version The current version.
202 *
203 * @return void
204 * @since 2.4
205 */
206 public function migrate_configs( $current_version ) {
207 if (
208 version_compare( $current_version, '2.2', '>=' )
209 && version_compare( $current_version, '2.4', '<' )
210 ) {
211 $config_component = wd_di()->get( Backup_Settings::class );
212 $prev_data = $config_component->backup_data();
213 if ( empty( $prev_data ) ) {
214 return;
215 }
216 $adapter = wd_di()->get( Config_Adapter::class );
217 $migrated_data = $adapter->upgrade( $prev_data );
218 $config_component->restore_data( $migrated_data, 'migration' );
219 // Hide Onboard page.
220 update_site_option( 'wp_defender_shown_activator', true );
221
222 $configs = $config_component->get_configs();
223 if ( ! empty( $configs ) ) {
224 foreach ( $configs as $k => $config ) {
225 if (
226 $config_component->verify_config_data( $config )
227 && ! $config_component->check_for_new_structure( $config['configs'] )
228 ) {
229 $new_data = $config;
230 $new_data['configs'] = $adapter->upgrade( $config['configs'] );
231 // Import config 'strings' and the active tag if a config has it.
232 if ( isset( $config['is_active'] ) ) {
233 $new_data['is_active'] = $config['is_active'];
234 }
235 $new_data['strings'] = $config_component->import_module_strings( $new_data );
236 // Update config data.
237 update_site_option( $k, $new_data );
238 }
239 }
240 }
241 }
242 // For older versions we do not use old models, e.g. for version < 2.2. So the default values will be used.
243 }
244
245 /**
246 * Upgrade to version 2.4.2.
247 *
248 * @return void
249 */
250 private function upgrade_2_4_2(): void {
251 // Update Scan settings.
252 $model_settings = wd_di()->get( Scan_Settings::class );
253 $option = get_site_option( $model_settings->get_table() );
254 if ( ! empty( $option ) && ! is_array( $option ) ) {
255 $old_settings = json_decode( $option, true );
256 if ( is_array( $old_settings ) && isset( $old_settings['max_filesize'] ) ) {
257 $model_settings->filesize = (int) $old_settings['max_filesize'];
258 $model_settings->save();
259 }
260 }
261 // Update 'reminder_duration' value inside the Security Key tweak.
262 $old_settings = get_site_option( 'wd_hardener_settings' );
263 if ( ! empty( $old_settings ) && ! is_array( $old_settings ) ) {
264 $old_settings = json_decode( $old_settings, true );
265 $tweak_sec_key = wd_di()->get( Security_Key::class );
266
267 if ( is_array( $old_settings ) && isset( $old_settings['data']['securityReminderDuration'] )
268 && in_array(
269 $old_settings['data']['securityReminderDuration'],
270 $tweak_sec_key->reminder_frequencies(),
271 true
272 )
273 ) {
274 $tweak_sec_key->update_option( 'reminder_duration', $old_settings['data']['securityReminderDuration'] );
275 }
276 }
277 }
278
279 /**
280 * Migrate value of scan setting from 'integrity_check' to 'check_core'.
281 *
282 * @return void
283 * @since 2.4.7
284 */
285 private function migrate_scan_integrity_check(): void {
286 $model = new Scan_Settings();
287 $model->check_core = (bool) $model->integrity_check;
288 $model->save();
289 }
290
291 /**
292 * Run an upgrade/installation.
293 *
294 * @return void
295 * @throws SodiumException If the encryption fails.
296 */
297 public function run() {
298 // Sometimes multiple requests come at the same time. So we will only count the web requests.
299 if ( defined( 'DOING_AJAX' ) || defined( 'DOING_CRON' ) ) {
300 return;
301 }
302
303 $db_version = get_site_option( 'wd_db_version' );
304 if ( empty( $db_version ) ) {
305 update_site_option( 'wd_db_version', DEFENDER_DB_VERSION );
306 update_site_option( Feature_Modal::FEATURE_SLUG, true );
307
308 return;
309 }
310
311 if ( DEFENDER_DB_VERSION === $db_version ) {
312 return;
313 }
314 $this->create_database_tables();
315 $this->maybe_show_new_features( $db_version );
316 $this->migrate_configs( $db_version );
317
318 if ( version_compare( $db_version, '2.2.9', '<' ) ) {
319 $this->migrate_security_headers();
320 }
321 if ( version_compare( $db_version, '2.4.2', '<' ) ) {
322 $this->upgrade_2_4_2();
323 }
324 if ( version_compare( $db_version, '2.4.7', '<' ) ) {
325 $this->index_database();
326 $this->migrate_scan_integrity_check();
327 }
328 if ( version_compare( $db_version, '2.4.10', '<' ) ) {
329 $this->upgrade_2_4_10();
330 }
331 if ( version_compare( $db_version, '2.5.0', '<' ) ) {
332 $this->upgrade_2_5_0();
333 }
334 if ( version_compare( $db_version, '2.5.6', '<' ) ) {
335 $this->upgrade_2_5_6();
336 }
337 if ( version_compare( $db_version, '2.6.1', '<' ) ) {
338 $this->upgrade_2_6_1();
339 }
340 if ( version_compare( $db_version, '2.7.0', '<' ) ) {
341 $this->upgrade_2_7_0();
342 }
343 if ( version_compare( $db_version, '2.8.0', '<' ) ) {
344 $this->upgrade_2_8_0();
345 }
346 if ( version_compare( $db_version, '2.8.3', '<' ) ) {
347 $this->upgrade_2_8_3();
348 }
349 if ( version_compare( $db_version, '3.2.0', '<' ) ) {
350 $this->upgrade_3_2_0();
351 }
352 if ( version_compare( $db_version, '3.3.0', '<' ) ) {
353 $this->upgrade_3_3_0();
354 }
355 if ( version_compare( $db_version, '3.3.1', '<' ) ) {
356 $this->upgrade_3_3_1();
357 }
358 if ( version_compare( $db_version, '3.3.3', '<' ) ) {
359 $this->upgrade_3_3_3();
360 }
361 if ( version_compare( $db_version, '3.5.0', '<' ) ) {
362 $this->upgrade_3_5_0();
363 }
364 if ( version_compare( $db_version, '3.8.0', '<' ) ) {
365 $this->upgrade_3_8_0();
366 }
367 if ( version_compare( $db_version, '3.8.2', '<' ) ) {
368 $this->upgrade_3_8_2();
369 }
370 if ( version_compare( $db_version, '3.9.0', '<' ) ) {
371 $this->upgrade_3_9_0();
372 }
373 if ( version_compare( $db_version, '3.11.0', '<' ) ) {
374 $this->upgrade_3_11_0();
375 }
376 if ( version_compare( $db_version, '3.12.0', '<' ) ) {
377 $this->upgrade_3_12_0();
378 }
379 if ( version_compare( $db_version, '4.0.0', '<' ) ) {
380 $this->upgrade_4_0_0();
381 }
382 if ( version_compare( $db_version, '4.1.0', '<' ) ) {
383 $this->upgrade_4_1_0();
384 }
385 if ( version_compare( $db_version, '4.2.0', '<' ) ) {
386 $this->upgrade_4_2_0();
387 }
388 if ( version_compare( $db_version, '4.5.1', '<' ) ) {
389 $this->upgrade_4_5_1();
390 }
391 if ( version_compare( $db_version, '4.6.0', '<' ) ) {
392 $this->upgrade_4_6_0();
393 }
394 if ( version_compare( $db_version, '4.7.2', '<' ) ) {
395 $this->upgrade_4_7_2();
396 }
397 if ( version_compare( $db_version, '4.8.2', '<' ) ) {
398 $this->upgrade_4_8_2();
399 }
400 if ( version_compare( $db_version, '4.9.0', '<' ) ) {
401 $this->upgrade_4_9_0();
402 }
403 if ( version_compare( $db_version, '5.0.0', '<' ) ) {
404 $this->upgrade_5_0_0();
405 }
406 if ( version_compare( $db_version, '5.0.2', '<' ) ) {
407 $this->upgrade_5_0_2();
408 }
409 if ( version_compare( $db_version, '5.1.1', '<' ) ) {
410 $this->upgrade_5_1_1();
411 }
412 if ( version_compare( $db_version, '5.2.0', '<' ) ) {
413 $this->upgrade_5_2_0();
414 }
415 if ( version_compare( $db_version, '5.3.0', '<' ) ) {
416 $this->upgrade_5_3_0();
417 }
418 if ( version_compare( $db_version, '5.3.1', '<' ) ) {
419 $this->upgrade_5_3_1();
420 }
421 if ( version_compare( $db_version, '5.4.0', '<' ) ) {
422 $this->upgrade_5_4_0();
423 }
424 if ( version_compare( $db_version, '5.5.0', '<' ) ) {
425 $this->upgrade_5_5_0();
426 }
427 if ( version_compare( $db_version, '5.6.0', '<' ) ) {
428 $this->upgrade_5_6_0();
429 }
430 // This is not a new installation. Make a mark.
431 defender_no_fresh_install();
432 // Don't run any function below this line.
433 update_site_option( 'wd_db_version', DEFENDER_DB_VERSION );
434 }
435
436 /**
437 * Index necessary columns.
438 * Sometimes this function call twice that's why we have to check index already exists or not.
439 * `dbDelta` not work on `ALTER TABLE` query, so we had to use $wpdb->query().
440 *
441 * @return void
442 * @since 2.4.7
443 */
444 private function index_database(): void {
445 global $wpdb;
446 $wpdb->hide_errors();
447
448 $this->add_index_to_defender_email_log( $wpdb );
449 $this->add_index_to_defender_audit_log( $wpdb );
450 $this->add_index_to_defender_scan_item( $wpdb );
451 $this->add_index_to_defender_lockout_log( $wpdb );
452 $this->add_index_to_defender_lockout( $wpdb );
453 }
454
455 /**
456 * Add index to defender_email_log.
457 *
458 * @param wpdb $wpdb WordPress Database object.
459 *
460 * @return void
461 * @since 2.4.7
462 */
463 private function add_index_to_defender_email_log( $wpdb ) {
464 // Check index already exists or not.
465 $result = $wpdb->get_row( "SHOW INDEX FROM {$wpdb->base_prefix}defender_email_log WHERE Key_name = 'source';", ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
466 if ( is_array( $result ) ) {
467 return;
468 }
469
470 $wpdb->query( "ALTER TABLE {$wpdb->base_prefix}defender_email_log ADD INDEX `source` (`source`);" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
471 }
472
473 /**
474 * Add index to defender_audit_log.
475 *
476 * @param wpdb $wpdb WordPress Database object.
477 *
478 * @return void
479 * @since 2.4.7
480 */
481 private function add_index_to_defender_audit_log( $wpdb ) {
482 // Check index already exists or not.
483 $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
484 if ( is_array( $result ) ) {
485 return;
486 }
487
488 $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
489 }
490
491 /**
492 * Add index to defender_scan_item.
493 *
494 * @param wpdb $wpdb WordPress Database object.
495 *
496 * @return void
497 * @since 2.4.7
498 */
499 private function add_index_to_defender_scan_item( $wpdb ) {
500 // Check index already exists or not.
501 $result = $wpdb->get_row( "SHOW INDEX FROM {$wpdb->base_prefix}defender_scan_item WHERE Key_name = 'type';", ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
502 if ( is_array( $result ) ) {
503 return;
504 }
505
506 $wpdb->query( "ALTER TABLE {$wpdb->base_prefix}defender_scan_item ADD INDEX `type` (`type`), ADD INDEX `status` (`status`);" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
507 }
508
509 /**
510 * Add index to defender_lockout_log.
511 *
512 * @param wpdb $wpdb WordPress Database object.
513 *
514 * @return void
515 * @since 2.4.7
516 */
517 private function add_index_to_defender_lockout_log( $wpdb ) {
518 // Check index already exists or not.
519 $result = $wpdb->get_row( "SHOW INDEX FROM {$wpdb->base_prefix}defender_lockout_log WHERE Key_name = 'ip';", ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
520 if ( is_array( $result ) ) {
521 return;
522 }
523
524 $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
525 }
526
527 /**
528 * Add index to defender_lockout.
529 *
530 * @param wpdb $wpdb WordPress Database object.
531 *
532 * @return void
533 * @since 2.4.7
534 */
535 private function add_index_to_defender_lockout( $wpdb ) {
536 // Check index already exists or not.
537 $result = $wpdb->get_row( "SHOW INDEX FROM {$wpdb->base_prefix}defender_lockout WHERE Key_name = 'ip';", ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
538 if ( is_array( $result ) ) {
539 return;
540 }
541
542 $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
543 }
544
545 /**
546 * Upgrade to 2.4.10.
547 *
548 * @return void
549 * @since 2.4.10
550 */
551 private function upgrade_2_4_10(): void {
552 $service = wd_di()->get( Backup_Settings::class );
553 $configs = Config_Hub_Helper::get_configs( $service );
554 $deprecated_keys = array(
555 // Reason: updated or removed some tweak slugs.
556 'security_key',
557 'wp-rest-api',
558 // Reason: moved the security headers to a separate module.
559 'sh-referrer-policy',
560 'sh-strict-transport',
561 'sh-xframe',
562 'sh-content-security',
563 'sh-content-type-options',
564 'sh-feature-policy',
565 'sh-xss-protection',
566 );
567 foreach ( $configs as $key => $config ) {
568 $is_updated = false;
569 // Remove deprecated 'data' key inside Security tweaks.
570 if ( isset( $config['configs']['security_tweaks']['data'] ) ) {
571 unset( $configs[ $key ]['configs']['security_tweaks']['data'] );
572 $is_updated = true;
573 }
574 // Remove deprecated keys in 'issues'.
575 if ( isset( $config['configs']['security_tweaks']['issues'] ) ) {
576 foreach ( $config['configs']['security_tweaks']['issues'] as $iss_key => $issue ) {
577 if ( in_array( $issue, $deprecated_keys, true ) ) {
578 unset( $configs[ $key ]['configs']['security_tweaks']['issues'][ $iss_key ] );
579 $is_updated = true;
580 }
581 }
582 }
583 // In 'ignore'.
584 if ( isset( $config['configs']['security_tweaks']['ignore'] ) ) {
585 foreach ( $config['configs']['security_tweaks']['ignore'] as $ign_key => $issue ) {
586 if ( in_array( $issue, $deprecated_keys, true ) ) {
587 unset( $configs[ $key ]['configs']['security_tweaks']['ignore'][ $ign_key ] );
588 $is_updated = true;
589 }
590 }
591 }
592 // In 'fixed'.
593 if ( isset( $config['configs']['security_tweaks']['fixed'] ) ) {
594 foreach ( $config['configs']['security_tweaks']['fixed'] as $fix_key => $issue ) {
595 if ( in_array( $issue, $deprecated_keys, true ) ) {
596 unset( $configs[ $key ]['configs']['security_tweaks']['fixed'][ $fix_key ] );
597 $is_updated = true;
598 }
599 }
600 }
601
602 if ( $is_updated ) {
603 update_site_option( $key, $configs[ $key ] );
604 Config_Hub_Helper::update_on_hub( $configs[ $key ] );
605 if ( isset( $config['is_active'] ) ) {
606 $model = new Model\Setting\Security_Tweaks();
607 $model->issues = $configs[ $key ]['configs']['security_tweaks']['issues'];
608 $model->ignore = $configs[ $key ]['configs']['security_tweaks']['ignore'];
609 $model->fixed = $configs[ $key ]['configs']['security_tweaks']['fixed'];
610 $model->save();
611 }
612 }
613 }
614 }
615
616 /**
617 * Upgrade to 2.5.0.
618 *
619 * @return void
620 * @since 2.5.0
621 */
622 private function upgrade_2_5_0(): void {
623 $model = wd_di()->get( Security_Headers::class );
624 // Directive ALLOW-FROM is deprecated. If header directive is ALLOW-FROM then set 'sameorigin'.
625 if ( isset( $model->sh_xframe_mode ) && 'allow-from' === $model->sh_xframe_mode ) {
626 $model->sh_xframe_mode = 'sameorigin';
627 $model->save();
628 }
629 /**
630 * Uncheck 'File change detection' option if there was checked only child 'Scan theme files' option and save
631 * settings. Also remove items for 'Scan theme files' without run Scan.
632 */
633 $scan_settings = new Scan_Settings();
634 if (
635 $scan_settings->integrity_check
636 && ! $scan_settings->check_core
637 && ! $scan_settings->check_plugins
638 ) {
639 $scan_settings->integrity_check = false;
640 $scan_settings->save();
641 }
642 }
643
644 /**
645 * Forces the addition of default lockout exclusions to the Notfound Lockout settings.
646 *
647 * @return void
648 */
649 private function force_nf_lockout_exclusions(): void {
650 $nf_settings = new Notfound_Lockout();
651 $allowlist = $nf_settings->get_lockout_list( 'allowlist' );
652 $default_allowlist = array( '.css', '.js', '.map' );
653 $is_save = false;
654 if ( ! empty( $allowlist ) ) {
655 foreach ( $default_allowlist as $item ) {
656 if ( ! in_array( $item, $allowlist, true ) ) {
657 $allowlist[] = $item;
658 $is_save = true;
659 }
660 }
661 $nf_settings->whitelist = implode( "\n", $allowlist );
662 } else {
663 $nf_settings->whitelist = ".css\n.js\n.map";
664 $is_save = true;
665 }
666 // Save it.
667 if ( $is_save ) {
668 $nf_settings->save();
669 }
670 }
671
672 /**
673 * Upgrade to 2.5.6.
674 *
675 * @return void
676 * @since 2.5.6
677 */
678 private function upgrade_2_5_6(): void {
679 $adapted_component = wd_di()->get( Legacy_Versions::class );
680 $issue_list = $adapted_component->get_scan_issue_data();
681 $ignored_list = $adapted_component->get_scan_ignored_data();
682 if ( ! empty( $issue_list ) || ! empty( $ignored_list ) ) {
683 $adapted_component->migrate_scan_data( $issue_list, $ignored_list );
684 $adapted_component->remove_old_scan_data( $issue_list, $ignored_list );
685 $adapted_component->change_onboarding_status();
686 }
687
688 // Trigger recurring cron schedule for autogenerate security salt.
689 $security_tweaks = wd_di()->get( Security_Tweaks::class );
690 $security_tweaks->get_security_key()->cron_schedule();
691 // Add some lockout extension to old installations forced.
692 $this->force_nf_lockout_exclusions();
693 }
694
695 /**
696 * Updates the body of the scan error email template.
697 *
698 * @param mixed $model The model containing the email template configuration.
699 *
700 * @return void
701 */
702 private function update_scan_error_send_body( $model ): void {
703 if (
704 isset( $model->configs['template']['error']['body'] )
705 && ! empty( $model->configs['template']['error']['body'] )
706 ) {
707 $needle = '{follow this link} and check the logs to see what casued the failure';
708 if ( false !== stripos( $model->configs['template']['error']['body'], $needle ) ) {
709 $model->configs['template']['error']['body'] = str_replace(
710 $needle,
711 'visit your site and run a manual scan',
712 $model->configs['template']['error']['body']
713 );
714 $model->save();
715 }
716 }
717 }
718
719 /**
720 * Upgrade to 2.6.1.
721 *
722 * @return void
723 * @since 2.6.1
724 */
725 private function upgrade_2_6_1(): void {
726 // Update the title of the basic config.
727 $config_component = wd_di()->get( Backup_Settings::class );
728 $configs = $config_component->get_configs();
729 if ( ! empty( $configs ) ) {
730 foreach ( $configs as $k => $config ) {
731 if ( 0 === strcmp( $config['name'], esc_html__( 'Basic config', 'defender-security' ) ) ) {
732 $config['name'] = esc_html__( 'Basic Config', 'defender-security' );
733
734 update_site_option( $k, $config );
735 delete_site_transient( Config_Hub_Helper::CONFIGS_TRANSIENT_KEY );
736 break;
737 }
738 }
739 }
740 // Update scan emails for 'When failed to scan' option.
741 $malware_notification = wd_di()->get( Malware_Notification::class );
742 $this->update_scan_error_send_body( $malware_notification );
743 $malware_report = wd_di()->get( Malware_Report::class );
744 $this->update_scan_error_send_body( $malware_report );
745 }
746
747 /**
748 * Update 2FA email template.
749 *
750 * @return void
751 */
752 private function update_2fa_send_body(): void {
753 $model = wd_di()->get( Two_Fa_Settings::class );
754 if ( isset( $model->email_body ) ) {
755 $model->email_body = 'Hi {{display_name}},
756
757 Your temporary password is {{passcode}}. To finish logging in, copy and paste the temporary password into the Password field on the login screen.';
758 $model->save();
759 }
760 }
761
762 /**
763 * Update Malware Scan email template.
764 *
765 * @return void
766 * @since 2.7.0
767 */
768 private function update_malware_scan_send_body(): void {
769 $models = array(
770 wd_di()->get( Malware_Notification::class ),
771 wd_di()->get( Malware_Report::class ),
772 );
773
774 foreach ( $models as $model ) {
775 $is_updated = false;
776 if ( ! empty( $model->configs['template']['error']['body'] ) ) {
777 $subject = $this->replace_scan_email_content_error( $model->configs['template']['error']['body'] );
778
779 if ( $model->configs['template']['error']['body'] !== $subject ) {
780 $is_updated = true;
781
782 $model->configs['template']['error']['body'] = $subject;
783 }
784 }
785
786 if ( ! empty( $model->configs['template']['found']['body'] ) ) {
787 $subject = $this->replace_scan_email_content_issue_found( $model->configs['template']['found']['body'] );
788
789 if ( $model->configs['template']['found']['body'] !== $subject ) {
790 $is_updated = true;
791
792 $model->configs['template']['found']['body'] = $subject;
793 }
794 }
795
796 if ( ! empty( $model->configs['template']['not_found']['body'] ) ) {
797 $subject = $this->replace_scan_email_content_issue_not_found( $model->configs['template']['not_found']['body'] );
798
799 if ( $model->configs['template']['not_found']['body'] !== $subject ) {
800 $is_updated = true;
801
802 $model->configs['template']['not_found']['body'] = $subject;
803 }
804 }
805
806 if ( true === $is_updated ) {
807 $model->save();
808 }
809 }
810 }
811
812 /**
813 * Update Malware Scan email template common parts.
814 *
815 * @param string $subject Email subject.
816 *
817 * @return string
818 * @since 2.7.0
819 */
820 private function update_malware_scan_send_body_common( $subject ): string {
821 $subject = preg_replace( '/(\R+)WP Defender here, reporting back from the front\./i', '', $subject );
822
823 return preg_replace( '/(\R+)Stay Safe,(\R+)WP Defender(\R+)WPMU DEV Superhero(\s*)$/i', '', $subject );
824 }
825
826 /**
827 * Upgrade to 2.7.0.
828 *
829 * @return void
830 * @since 2.7.0
831 */
832 private function upgrade_2_7_0(): void {
833 $malware_report = wd_di()->get( Malware_Report::class );
834 $scan_settings = wd_di()->get( Scan_Settings::class );
835 // Migrate data from Malware_Report to Scan settings.
836 $scan_settings->scheduled_scanning = Notification::STATUS_ACTIVE === $malware_report->status;
837 $scan_settings->frequency = $malware_report->frequency;
838 $scan_settings->day = $malware_report->day;
839 $scan_settings->day_n = (int) $malware_report->day_n;
840 $scan_settings->time = $malware_report->time;
841 $scan_settings->save();
842
843 $this->update_2fa_send_body();
844 $this->update_malware_scan_send_body();
845 }
846
847 /**
848 * Update in_house recipients empty role.
849 *
850 * @return void
851 * @since 2.8.0
852 */
853 private function update_in_house_recipients_empty_role(): void {
854 $models = array(
855 wd_di()->get( Audit_Report::class ),
856 wd_di()->get( Firewall_Notification::class ),
857 wd_di()->get( Firewall_Report::class ),
858 wd_di()->get( Malware_Notification::class ),
859 wd_di()->get( Malware_Report::class ),
860 wd_di()->get( Tweak_Reminder::class ),
861 );
862
863 foreach ( $models as $model ) {
864 if ( ! empty( $model->in_house_recipients ) && is_array( $model->in_house_recipients ) ) {
865 $is_updated = false;
866
867 foreach ( $model->in_house_recipients as &$recipient ) {
868 if ( empty( $recipient['role'] ) ) {
869 $is_updated = true;
870 $recipient['role'] = $this->get_current_user_role( $recipient['id'] );
871 }
872 }
873
874 if ( true === $is_updated ) {
875 $model->save();
876 }
877 }
878 }
879 }
880
881 /**
882 * Delete temp geolite file from /wp-admin directory.
883 *
884 * @return void
885 * @since 2.8.0
886 */
887 private function delete_tmp_geolite_file(): void {
888 $pattern = ABSPATH . 'wp-admin/geolite2-country*.tar.gz';
889 array_map( 'wp_delete_file', glob( $pattern ) );
890 }
891
892 /**
893 * Migrate old keys to checked options.
894 *
895 * @return void
896 * @since 2.8.0
897 */
898 private function update_2fa_methods(): void {
899 $settings = wd_di()->get( Two_Fa_Settings::class );
900 if ( $settings->enabled ) {
901 $service = wd_di()->get( Two_Fa_Component::class );
902 $query = new WP_User_Query(
903 array(
904 'blog_id' => 0,
905 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
906 array(
907 'key' => Totp::TOTP_AUTH_KEY,
908 'value' => true,
909 ),
910 ),
911 'fields' => 'ID',
912 )
913 );
914 if ( $query->get_total() > 0 ) {
915 // The data is independent of the user's data.
916 $providers = $service->get_providers();
917 $totp_slug = Totp::$slug;
918 // Add TOTP slug.
919 $enabled_providers = array( $totp_slug );
920 // If 'Enable lost phone' option is checked then we add the slug of the Email provider too.
921 if ( true === $settings->lost_phone ) {
922 $enabled_providers[] = Fallback_Email::$slug;
923 }
924 // Add slugs to the list of the enabled providers.
925 $enabled_providers = array_intersect( $enabled_providers, array_keys( $providers ) );
926 foreach ( $query->get_results() as $user_id ) {
927 // If 2FA module is configured for the current user.
928 if ( $service->is_enabled_otp_for_user( $user_id ) ) {
929 update_user_meta( $user_id, Two_Fa_Component::ENABLED_PROVIDERS_USER_KEY, $enabled_providers );
930 // Set TOTP as the default auth provider.
931 update_user_meta( $user_id, Two_Fa_Component::DEFAULT_PROVIDER_USER_KEY, $totp_slug );
932 }
933 }
934 }
935 }
936 }
937
938 /**
939 * Upgrade to 2.8.0.
940 *
941 * @return void
942 * @since 2.8.0
943 */
944 private function upgrade_2_8_0(): void {
945 $this->update_in_house_recipients_empty_role();
946 $this->delete_tmp_geolite_file();
947 $this->update_2fa_methods();
948 }
949
950 /**
951 * Add column country_iso_code and index to table.
952 *
953 * @return void
954 * @since 2.8.1
955 */
956 private function add_country_iso_code_column(): void {
957 if ( ! function_exists( 'maybe_add_column' ) ) {
958 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
959 }
960
961 global $wpdb;
962
963 $table_name = $wpdb->base_prefix . 'defender_lockout_log';
964 $column_name = 'country_iso_code';
965 $create_ddl = "ALTER TABLE {$table_name} ADD {$column_name} CHAR(2) DEFAULT NULL";
966
967 maybe_add_column( $table_name, $column_name, $create_ddl );
968
969 $prev_val = $wpdb->hide_errors();
970 $wpdb->query( "CREATE INDEX `country_iso_code` ON {$wpdb->base_prefix}defender_lockout_log (`country_iso_code`)" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
971 $wpdb->show_errors( $prev_val );
972 }
973
974 /**
975 * Upgrade to 2.8.3.
976 *
977 * @return void
978 * @since 2.8.3
979 */
980 private function upgrade_2_8_3(): void {
981 $this->add_country_iso_code_column();
982 }
983
984 /**
985 * Upgrade to 3.2.0.
986 *
987 * @return void
988 * @since 3.2.0
989 */
990 private function upgrade_3_2_0(): void {
991 $model = wd_di()->get( Two_Fa_Settings::class );
992
993 if ( ! empty( $model->app_title ) ) {
994 $model->app_title = wp_specialchars_decode( $model->app_title, ENT_QUOTES );
995 }
996
997 $model->custom_graphic_type = Two_Fa_Settings::CUSTOM_GRAPHIC_TYPE_UPLOAD;
998 $model->save();
999 }
1000
1001 /**
1002 * Upgrade to 3.3.0.
1003 *
1004 * @return void
1005 * @since 3.3.0
1006 */
1007 private function upgrade_3_3_0(): void {
1008 $this->update_webauthn_user_handle();
1009 $this->add_ua_lockout_to_firewall_notification();
1010 }
1011
1012 /**
1013 * Update webauthn 'userHandle' to make it independent of salt keys.
1014 *
1015 * @return void
1016 * @since 3.3.0
1017 */
1018 private function update_webauthn_user_handle(): void {
1019 global $wpdb;
1020 $service = wd_di()->get( Webauthn::class );
1021
1022 if ( is_multisite() ) {
1023 $offset = 0;
1024 $limit = 100;
1025 $blogs = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1026 $wpdb->prepare( "SELECT blog_id FROM {$wpdb->blogs} LIMIT %d, %d", $offset, $limit ),
1027 ARRAY_A
1028 );
1029 while ( ! empty( $blogs ) && is_array( $blogs ) ) {
1030 foreach ( $blogs as $blog ) {
1031 switch_to_blog( $blog['blog_id'] );
1032
1033 $this->update_webauthn_user_handle_core( $service );
1034
1035 restore_current_blog();
1036 }
1037 $offset += $limit;
1038 $blogs = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1039 $wpdb->prepare( "SELECT blog_id FROM {$wpdb->blogs} LIMIT %d, %d", $offset, $limit ),
1040 ARRAY_A
1041 );
1042 }
1043 } else {
1044 $this->update_webauthn_user_handle_core( $service );
1045 }
1046 }
1047
1048 /**
1049 * Core method for updating webauthn 'userHandle' to make it independent of salt keys.
1050 *
1051 * @param object $service Webauthn service instance.
1052 *
1053 * @return void
1054 * @since 3.3.0
1055 */
1056 private function update_webauthn_user_handle_core( object $service ) {
1057 $data = get_option( $this->option_prefix . $service::CREDENTIAL_OPTION_KEY );
1058 $data = is_string( $data ) ? json_decode( $data, true ) : array();
1059 delete_option( $this->option_prefix . $service::CREDENTIAL_OPTION_KEY );
1060 if ( ! is_array( $data ) || 0 === count( $data ) ) {
1061 return;
1062 }
1063
1064 $users = get_users( array( 'fields' => array( 'ID', 'user_login', 'display_name' ) ) );
1065
1066 foreach ( $users as $user ) {
1067 if ( empty( $data ) ) {
1068 break;
1069 }
1070
1071 $user_credentials = array();
1072 foreach ( $data as $key => $item ) {
1073 $old_hash = hash( 'sha256', $user->user_login . '-' . $user->display_name . '-' . AUTH_SALT );
1074 // This is not obfuscation. Just encode the hashed value into a base64 string.
1075 $old_base64_hash = base64_encode( $old_hash ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
1076 $old_base64_hash = preg_replace( '/\=+$/', '', $old_base64_hash );
1077
1078 if (
1079 isset( $item['credential_source']['userHandle'] ) &&
1080 $item['credential_source']['userHandle'] === $old_base64_hash
1081 ) {
1082 $new_hash = $this->get_user_hash( $user->user_login );
1083 // This is not obfuscation. Just encode the hashed value into a base64 string.
1084 $new_base64_hash = base64_encode( $new_hash ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
1085 $new_base64_hash = preg_replace( '/\=+$/', '', $new_base64_hash );
1086 $item['user'] = $new_hash;
1087 $item['credential_source']['userHandle'] = $new_base64_hash;
1088 $user_credentials[ $key ] = $item;
1089 unset( $data[ $key ] );
1090 }
1091 }
1092
1093 if ( ! empty( $user_credentials ) ) {
1094 $service->setCredentials( (int) $user->ID, $user_credentials );
1095 }
1096 }
1097 }
1098
1099 /**
1100 * Add the ability to send notifications about UA-lockout.
1101 *
1102 * @return void
1103 */
1104 private function add_ua_lockout_to_firewall_notification(): void {
1105 $model = wd_di()->get( Firewall_Notification::class );
1106 if ( ! isset( $model->configs['ua_lockout'] ) ) {
1107 $model->configs['ua_lockout'] = false;
1108 $model->save();
1109 }
1110 }
1111
1112 /**
1113 * Upgrade to 3.3.1: extra security steps.
1114 *
1115 * @return void
1116 */
1117 private function upgrade_3_3_1(): void {
1118 // Update tokens.
1119 $query = new WP_User_Query(
1120 array(
1121 'blog_id' => 0,
1122 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
1123 array(
1124 'key' => Two_Fa_Component::TOKEN_USER_KEY,
1125 'compare' => 'EXISTS',
1126 ),
1127 ),
1128 'fields' => 'ID',
1129 )
1130 );
1131 if ( $query->get_total() > 0 ) {
1132 // Hashed tokens.
1133 foreach ( $query->get_results() as $user_id ) {
1134 $token = bin2hex( Crypt::random_bytes( 32 ) );
1135 update_user_meta( $user_id, Two_Fa_Component::TOKEN_USER_KEY, wp_hash( $user_id . $token ) );
1136 }
1137 }
1138 // Update fallback codes.
1139 $query = new WP_User_Query(
1140 array(
1141 'blog_id' => 0,
1142 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
1143 array(
1144 'key' => Fallback_Email::FALLBACK_BACKUP_CODE_KEY,
1145 'compare' => 'EXISTS',
1146 ),
1147 ),
1148 'fields' => 'ID',
1149 )
1150 );
1151 if ( $query->get_total() > 0 ) {
1152 // Hashed codes.
1153 foreach ( $query->get_results() as $user_id ) {
1154 $backup_code = get_user_meta( $user_id, Fallback_Email::FALLBACK_BACKUP_CODE_KEY, true );
1155 if ( ! empty( $backup_code ) && isset( $backup_code['code'], $backup_code['time'] ) ) {
1156 update_user_meta(
1157 $user_id,
1158 Fallback_Email::FALLBACK_BACKUP_CODE_KEY,
1159 array(
1160 'code' => wp_hash( $backup_code['code'] ),
1161 'time' => $backup_code['time'],
1162 )
1163 );
1164 }
1165 }
1166 }
1167 }
1168
1169 /**
1170 * Upgrade to 3.3.3: update 2FA flow for secret keys.
1171 *
1172 * @return void
1173 * @throws SodiumException If the encryption fails.
1174 * @since 3.3.3
1175 */
1176 private function upgrade_3_3_3(): void {
1177 // Create a file with a random key if it doesn't exist.
1178 if ( ( new Crypt() )->create_key_file() ) {
1179 // Get user data.
1180 $query = new WP_User_Query(
1181 array(
1182 'blog_id' => 0,
1183 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
1184 array(
1185 'key' => Totp::TOTP_SODIUM_SECRET_KEY,
1186 'compare' => 'EXISTS',
1187 ),
1188 ),
1189 'fields' => 'ID',
1190 )
1191 );
1192 if ( $query->get_total() > 0 ) {
1193 $service = wd_di()->get( Two_Fa_Component::class );
1194 foreach ( $query->get_results() as $user_id ) {
1195 // If we have the old states (cleartext, pub key) then re-encrypt via Sodium and save it.
1196 if ( ! $service->maybe_update( $user_id ) ) {
1197 // Otherwise just encrypt it via Sodium.
1198 $plaintext = defender_generate_random_string( Totp::TOTP_LENGTH, TOTP::TOTP_CHARACTERS );
1199 $new_key = Crypt::get_encrypted_data( $plaintext );
1200 if ( ! is_wp_error( $new_key ) ) {
1201 // Remove an old key.
1202 delete_user_meta( $user_id, Totp::TOTP_SECRET_KEY );
1203 // Update a new one.
1204 update_user_meta( $user_id, Totp::TOTP_SODIUM_SECRET_KEY, $new_key );
1205 }
1206 }
1207 }
1208 }
1209 }
1210 }
1211
1212 /**
1213 * Upgrade to 3.5.0: Delete config transient.
1214 *
1215 * @return void
1216 */
1217 private function upgrade_3_5_0(): void {
1218 delete_site_transient( Config_Hub_Helper::CONFIGS_TRANSIENT_KEY );
1219 }
1220
1221 /**
1222 * Upgrade to 3.8.0: Remove permanently banned ip record from `defender_lockout` table.
1223 *
1224 * @return void
1225 * @since 3.8.0
1226 */
1227 private function upgrade_3_8_0(): void {
1228 global $wpdb;
1229
1230 $blacklist_settings = new Model_Blacklist_Lockout();
1231 $blacklist = $blacklist_settings->get_list();
1232
1233 $offset = 0;
1234 $length = 100;
1235 $table = $wpdb->base_prefix . 'defender_lockout';
1236 // Variable within condition is for comparison.
1237 while ( $blacklist_chunk = array_slice( $blacklist, $offset, $length ) ) { // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
1238 $sql = "DELETE FROM {$table} WHERE status = 'blocked' AND ip IN (" . implode( ', ', array_fill( 0, count( $blacklist_chunk ), '%s' ) ) . ')';
1239
1240 $query = call_user_func_array(
1241 array( $wpdb, 'prepare' ),
1242 array_merge( array( $sql ), $blacklist_chunk )
1243 );
1244 // SQL is prepared here. So we don't need to prepare it again.
1245 $wpdb->query( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery
1246
1247 $offset += $length;
1248 }
1249
1250 $this->update_malware_notification_config();
1251 $this->update_malware_notification_setting();
1252 }
1253
1254 /**
1255 * Update malware notification config.
1256 *
1257 * @return void
1258 * @since 3.8.0
1259 */
1260 private function update_malware_notification_config(): void {
1261 $service = wd_di()->get( Backup_Settings::class );
1262 $configs = Config_Hub_Helper::get_configs( $service );
1263
1264 foreach ( $configs as $key => $config ) {
1265 $is_updated = false;
1266
1267 if ( isset( $config['configs']['scan']['email_subject_issue_found'] ) ) {
1268 $subject = $this->replace_scan_email_subject_issue_found( $config['configs']['scan']['email_subject_issue_found'] );
1269 $configs[ $key ]['configs']['scan']['email_subject_issue_found'] = $subject;
1270 $is_updated = true;
1271 }
1272 if ( isset( $config['configs']['scan']['email_content_issue_found'] ) ) {
1273 $content = $this->replace_scan_email_content_issue_found( $config['configs']['scan']['email_content_issue_found'] );
1274 $content = $this->replace_scan_email_content_issue_found_new( $content );
1275 $configs[ $key ]['configs']['scan']['email_content_issue_found'] = $content;
1276 $is_updated = true;
1277 }
1278
1279 if ( isset( $config['configs']['scan']['email_content_issue_not_found'] ) ) {
1280 $content = $this->replace_scan_email_content_issue_not_found( $config['configs']['scan']['email_content_issue_not_found'] );
1281 $configs[ $key ]['configs']['scan']['email_content_issue_not_found'] = $content;
1282 $is_updated = true;
1283 }
1284
1285 if ( isset( $config['configs']['scan']['email_content_error'] ) ) {
1286 $content = $this->replace_scan_email_content_error( $config['configs']['scan']['email_content_error'] );
1287 $configs[ $key ]['configs']['scan']['email_content_error'] = $content;
1288 $is_updated = true;
1289 }
1290
1291 if ( true === $is_updated ) {
1292 update_site_option( $key, $configs[ $key ] );
1293 Config_Hub_Helper::update_on_hub( $configs[ $key ] );
1294 }
1295 }
1296
1297 delete_site_transient( Config_Hub_Helper::CONFIGS_TRANSIENT_KEY );
1298 }
1299
1300 /**
1301 * Update malware notification setting.
1302 *
1303 * @return void
1304 * @since 3.8.0
1305 */
1306 private function update_malware_notification_setting(): void {
1307 $models = array(
1308 wd_di()->get( Malware_Notification::class ),
1309 wd_di()->get( Malware_Report::class ),
1310 );
1311
1312 foreach ( $models as $model ) {
1313 $is_updated = false;
1314
1315 if ( isset( $model->configs['template']['found']['subject'] ) ) {
1316 $subject = $this->replace_scan_email_subject_issue_found( $model->configs['template']['found']['subject'] );
1317 $model->configs['template']['found']['subject'] = $subject;
1318 $is_updated = true;
1319 }
1320 if ( isset( $model->configs['template']['found']['body'] ) ) {
1321 $content = $this->replace_scan_email_content_issue_found( $model->configs['template']['found']['body'] );
1322 $content = $this->replace_scan_email_content_issue_found_new( $content );
1323 $model->configs['template']['found']['body'] = $content;
1324 $is_updated = true;
1325 }
1326
1327 if ( isset( $model->configs['template']['not_found']['body'] ) ) {
1328 $content = $this->replace_scan_email_content_issue_not_found( $model->configs['template']['not_found']['body'] );
1329 $model->configs['template']['not_found']['body'] = $content;
1330 $is_updated = true;
1331 }
1332
1333 if ( isset( $model->configs['template']['error']['body'] ) ) {
1334 $content = $this->replace_scan_email_content_error( $model->configs['template']['error']['body'] );
1335 $model->configs['template']['error']['body'] = $content;
1336 $is_updated = true;
1337 }
1338
1339 if ( true === $is_updated ) {
1340 $model->save();
1341 }
1342 }
1343 }
1344
1345 /**
1346 * Replace scan issue found email subject.
1347 *
1348 * @param string $subject Email subject.
1349 *
1350 * @return string
1351 * @since 3.8.0
1352 */
1353 private function replace_scan_email_subject_issue_found( string $subject ): string {
1354 $subject = str_replace(
1355 esc_html__( 'Scan of {SITE_URL} complete.', 'defender-security' ),
1356 esc_html__( 'Malware Scan of {SITE_URL} is complete.', 'defender-security' ),
1357 $subject
1358 );
1359
1360 return str_replace(
1361 esc_html__( '{ISSUES_COUNT} issues found.', 'defender-security' ),
1362 esc_html__( '{ISSUES_COUNT} issue(s) found.', 'defender-security' ),
1363 $subject
1364 );
1365 }
1366
1367 /**
1368 * Replace scan issue found email content.
1369 *
1370 * @param string $content Email content.
1371 *
1372 * @return string
1373 * @since 3.8.0
1374 */
1375 private function replace_scan_email_content_issue_found( string $content ): string {
1376 $content = $this->update_malware_scan_send_body_common( $content );
1377 $content = preg_replace(
1378 "/I(’|'|\\\')ve finished scanning \{SITE_URL\} for vulnerabilities and I found \{ISSUES_COUNT\} issues that you should take a closer look at!/i",
1379 '{ISSUES_COUNT} vulnerabilities were identified for {SITE_URL} during a Malware Scan. See details for each issue below.',
1380 $content
1381 );
1382
1383 return preg_replace( '/(\R+){ISSUES_LIST}/i', "\n\n{ISSUES_LIST}", $content );
1384 }
1385
1386 /**
1387 * Replace scan issue found email with new content.
1388 *
1389 * @param string $content Email content.
1390 *
1391 * @return string
1392 * @since 3.8.0
1393 */
1394 private function replace_scan_email_content_issue_found_new( string $content ): string {
1395 $content = str_replace(
1396 esc_html__( '{ISSUES_COUNT} vulnerabilities were identified for {SITE_URL} during a Malware Scan.', 'defender-security' ),
1397 esc_html__( 'Malware Scan identified {ISSUES_COUNT} issue(s) on {SITE_URL}.', 'defender-security' ),
1398 $content
1399 );
1400
1401 return str_replace(
1402 esc_html__( 'See details for each issue below.', 'defender-security' ),
1403 esc_html__( 'The identified issue(s) is/are listed below.', 'defender-security' ),
1404 $content
1405 );
1406 }
1407
1408 /**
1409 * Replace scan issue not found email content.
1410 *
1411 * @param string $content Email content.
1412 *
1413 * @return string
1414 * @since 3.8.0
1415 */
1416 private function replace_scan_email_content_issue_not_found( string $content ): string {
1417 $content = $this->update_malware_scan_send_body_common( $content );
1418 $content = preg_replace(
1419 "/I(’|'|\\\')ve finished scanning \{SITE_URL\} for vulnerabilities and I found nothing\. Well done for running such a tight ship!/i",
1420 'No vulnerabilities have been found for {SITE_URL}.',
1421 $content
1422 );
1423
1424 return preg_replace(
1425 "/(\R+)Keep up the good work! With regular security scans and a well-hardened installation you\'ll be just fine\./i",
1426 '',
1427 $content
1428 );
1429 }
1430
1431 /**
1432 * Replace scan error email content.
1433 *
1434 * @param string $content Email content.
1435 *
1436 * @return string
1437 * @since 3.8.0
1438 */
1439 private function replace_scan_email_content_error( string $content ): string {
1440 $content = $this->update_malware_scan_send_body_common( $content );
1441
1442 return str_replace( 'I couldn', 'We couldn', $content );
1443 }
1444
1445 /**
1446 * Upgrade to 3.8.2: Check XSS in existing configs.
1447 *
1448 * @return void
1449 */
1450 private function upgrade_3_8_2(): void {
1451 $service = wd_di()->get( Backup_Settings::class );
1452 $configs = Config_Hub_Helper::get_configs( $service );
1453
1454 foreach ( $configs as $key => $config ) {
1455 $is_updated = false;
1456
1457 if ( isset( $config['name'] ) ) {
1458 $prev_name = $config['name'];
1459 // Sanitize data.
1460 $new_name = sanitize_text_field( $config['name'] );
1461 if ( $prev_name !== $new_name ) {
1462 $is_updated = true;
1463 }
1464 $prev_desc = $config['description'];
1465 $new_desc = empty( $config['description'] ) ? '' : sanitize_textarea_field( $config['description'] );
1466 if ( $prev_desc !== $new_desc ) {
1467 $is_updated = true;
1468 }
1469 }
1470
1471 if ( true === $is_updated ) {
1472 update_site_option( $key, $configs[ $key ] );
1473 Config_Hub_Helper::update_on_hub( $configs[ $key ] );
1474 }
1475 }
1476
1477 delete_site_transient( Config_Hub_Helper::CONFIGS_TRANSIENT_KEY );
1478 }
1479
1480 /**
1481 * Upgrade to 3.9.0.
1482 *
1483 * @return void
1484 */
1485 private function upgrade_3_9_0(): void {
1486 global $wpdb;
1487
1488 $prev_val = $wpdb->hide_errors();
1489 // Changes for Lockout table.
1490 $wpdb->query( "ALTER TABLE{$wpdb->base_prefix}defender_lockout MODIFY COLUMN ip VARCHAR(45)" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1491 // Changes for Lockout log table.
1492 $wpdb->query( "ALTER TABLE{$wpdb->base_prefix}defender_lockout_log MODIFY COLUMN ip VARCHAR(45)" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1493 // Changes for Audit log table.
1494 $wpdb->query( "ALTER TABLE{$wpdb->base_prefix}defender_audit_log MODIFY COLUMN ip VARCHAR(45)" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1495 $wpdb->show_errors( $prev_val );
1496 }
1497
1498 /**
1499 * Upgrade to 3.11.0.
1500 *
1501 * @return void
1502 */
1503 private function upgrade_3_11_0(): void {
1504 // Move Global IP settings.
1505 $option = get_site_option( wd_di()->get( Model_Blacklist_Lockout::class )->get_table() );
1506 if ( ! empty( $option ) && is_string( $option ) ) {
1507 $old_settings = json_decode( $option, true );
1508 if ( is_array( $old_settings ) && isset( $old_settings['global_ip_list'] ) ) {
1509 $model_global_ip = wd_di()->get( Global_Ip_Lockout::class );
1510 $model_global_ip->enabled = (bool) $old_settings['global_ip_list'];
1511 $model_global_ip->save();
1512 }
1513 }
1514 }
1515
1516 /**
1517 * Upgrade to 3.12.0.
1518 *
1519 * @return void
1520 */
1521 private function upgrade_3_12_0(): void {
1522 delete_site_transient( Config_Hub_Helper::CONFIGS_TRANSIENT_KEY );
1523
1524 $service = wd_di()->get( Backup_Settings::class );
1525 $configs = Config_Hub_Helper::get_configs( $service );
1526
1527 foreach ( $configs as $key => &$config ) {
1528 if ( ! is_array( $config ) ) {
1529 continue;
1530 }
1531
1532 $config['is_removable'] = $config['is_removable'] ?? true;
1533
1534 // Update config data.
1535 update_site_option( $key, $config );
1536 }
1537
1538 set_site_transient(
1539 Config_Hub_Helper::CONFIGS_TRANSIENT_KEY,
1540 $configs,
1541 Config_Hub_Helper::CONFIGS_TRANSIENT_TIME
1542 );
1543 update_site_option( Config_Hub_Helper::CONFIGS_TRANSIENT_TIME_KEY, time() );
1544
1545 // Clear Global IP schedule hook to update event frequency.
1546 wp_clear_scheduled_hook( 'wpdef_fetch_global_ip_list' );
1547 }
1548
1549 /**
1550 * Upgrade to 4.0.0: Creates quarantine table.
1551 *
1552 * @return void
1553 */
1554 private function upgrade_4_0_0(): void {
1555 $bootstrap = wd_di()->get( Bootstrap::class );
1556 $bootstrap->create_table_quarantine();
1557 }
1558
1559 /**
1560 * Upgrade to 4.1.0.
1561 *
1562 * @return void
1563 */
1564 private function upgrade_4_1_0() {
1565 // Remove Maxmind DB directory from multisite subsites.
1566 if ( is_multisite() ) {
1567 global $wpdb, $wp_filesystem;
1568
1569 if ( is_null( $wp_filesystem ) ) {
1570 if ( ! function_exists( 'WP_Filesystem' ) ) {
1571 require_once ABSPATH . 'wp-admin/includes/file.php';
1572 }
1573 WP_Filesystem();
1574 }
1575 if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) {
1576 return;
1577 }
1578
1579 $main_site_id = get_main_site_id();
1580 $offset = 0;
1581 $limit = 100;
1582 $blogs = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1583 $wpdb->prepare(
1584 "SELECT blog_id FROM {$wpdb->blogs} WHERE blog_id != %d LIMIT %d, %d",
1585 $main_site_id,
1586 $offset,
1587 $limit
1588 ),
1589 ARRAY_A
1590 );
1591 while ( ! empty( $blogs ) && is_array( $blogs ) ) {
1592 foreach ( $blogs as $blog ) {
1593 switch_to_blog( $blog['blog_id'] );
1594
1595 $maxmind_dir = $this->get_tmp_path() . DIRECTORY_SEPARATOR . MaxMind_Geolocation::DB_DIRECTORY;
1596 $wp_filesystem->delete( $maxmind_dir, true );
1597
1598 restore_current_blog();
1599 }
1600 $offset += $limit;
1601 $blogs = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1602 $wpdb->prepare(
1603 "SELECT blog_id FROM {$wpdb->blogs} WHERE blog_id != %d LIMIT %d, %d",
1604 $main_site_id,
1605 $offset,
1606 $limit
1607 ),
1608 ARRAY_A
1609 );
1610 }
1611 }
1612 }
1613
1614 /**
1615 * Upgrade to 4.2.0.
1616 *
1617 * @return void
1618 */
1619 private function upgrade_4_2_0(): void {
1620 // Add the Tracking modal.
1621 update_site_option( Data_Tracking::TRACKING_SLUG, true );
1622 }
1623
1624 /**
1625 * Upgrade to 4.5.1.
1626 *
1627 * @return void
1628 */
1629 private function upgrade_4_5_1(): void {
1630 $service = wd_di()->get( Firewall::class );
1631 $service->auto_switch_ip_detection_option();
1632 $service->maybe_show_misconfigured_ip_detection_option_notice();
1633 }
1634
1635 /**
1636 * Upgrade to 4.6.0.
1637 *
1638 * @return void
1639 */
1640 private function upgrade_4_6_0(): void {
1641 // Create Unlockout table.
1642 $bootstrap = wd_di()->get( Bootstrap::class );
1643 $bootstrap->create_table_unlockout();
1644 }
1645
1646 /**
1647 * Upgrade to 4.7.2.
1648 *
1649 * @return void
1650 */
1651 private function upgrade_4_7_2(): void {
1652 $model = wd_di()->get( Two_Fa_Settings::class );
1653
1654 if ( isset( $model->email_body ) ) {
1655 $model->email_body = 'Hi {{display_name}},
1656
1657 Your temporary password is {{passcode}}
1658 To complete your login, copy and paste the temporary password into the Password field on the login screen.';
1659 $model->save();
1660 }
1661 }
1662
1663 /**
1664 * Upgrade to 4.8.2.
1665 *
1666 * @return void
1667 */
1668 private function upgrade_4_8_2(): void {
1669 $xff = defender_get_data_from_request( 'HTTP_X_FORWARDED_FOR', 's' );
1670 if (
1671 ! ( is_string( $xff ) && 0 < strlen( $xff ) ) &&
1672 Firewall::is_switched_ip_detection_notice( Firewall::IP_DETECTION_XFF_SHOW_SLUG )
1673 ) {
1674 delete_site_option( Firewall::IP_DETECTION_XFF_SHOW_SLUG );
1675 }
1676 }
1677
1678 /**
1679 * Upgrade to 4.9.0.
1680 *
1681 * @return void
1682 */
1683 private function upgrade_4_9_0(): void {
1684 $model = wd_di()->get( Model_Firewall::class );
1685
1686 $model->ip_detection_type = 'manual';
1687 $model->save();
1688
1689 // Add tracking.
1690 $firewall_analytics = wd_di()->get( Firewall_Analytics::class );
1691 $detection_method = Firewall_Analytics::get_detection_method_label(
1692 $model->ip_detection_type,
1693 $model->http_ip_header
1694 );
1695
1696 $firewall_analytics->track_feature(
1697 Firewall_Analytics::EVENT_IP_DETECTION,
1698 array( Firewall_Analytics::PROP_IP_DETECTION => $detection_method )
1699 );
1700 }
1701
1702 /**
1703 * Upgrade.
1704 *
1705 * @return void
1706 */
1707 private function upgrade_5_0_0(): void {
1708 update_site_option( \WP_Defender\Component\IP\Antibot_Global_Firewall::NOTICE_SLUG, true );
1709 }
1710
1711 /**
1712 * Upgrade to 5.0.2: Clear the blocklist count. Also set the whitelist server public IP.
1713 *
1714 * @return void
1715 */
1716 private function upgrade_5_0_2(): void {
1717 delete_site_transient( 'wpdef_antibot_global_firewall_db_blocklist_count' );
1718 wd_di()->get( Firewall::class )->set_whitelist_server_public_ip();
1719 }
1720
1721 /**
1722 * Upgrade to 5.1.1: Migrate IP detection option to Automatic from Manual > All headers.
1723 *
1724 * @return void
1725 */
1726 private function upgrade_5_1_1(): void {
1727 $model = wd_di()->get( Model_Firewall::class );
1728 if ( 'manual' === $model->ip_detection_type && '' === $model->http_ip_header ) {
1729 $model->ip_detection_type = 'automatic';
1730 $model->http_ip_header = 'REMOTE_ADDR';
1731 $model->save();
1732 wd_di()->get( \WP_Defender\Component\Smart_Ip_Detection::class )->smart_ip_detection_ping();
1733 // Add tracking.
1734 $firewall_analytics = wd_di()->get( Firewall_Analytics::class );
1735 $detection_method = Firewall_Analytics::get_detection_method_label( 'automatic', '' );
1736
1737 $firewall_analytics->track_feature(
1738 Firewall_Analytics::EVENT_IP_DETECTION,
1739 array( Firewall_Analytics::PROP_IP_DETECTION => $detection_method )
1740 );
1741 }
1742 }
1743 /**
1744 * Update UA blocklist.
1745 *
1746 * @return void
1747 */
1748 private function update_ua_blocklist(): void {
1749 $settings = wd_di()->get( User_Agent_Lockout::class );
1750 $blacklist = $settings->get_lockout_list( 'blocklist', false );
1751 if ( empty( $blacklist ) ) {
1752 return;
1753 }
1754 $blacklist = array_filter(
1755 $blacklist,
1756 function ( $agent ) {
1757 return false === stripos( $agent, 'ahrefsbot' ) && false === stripos( $agent, 'semrushbot' );
1758 }
1759 );
1760 $settings->blacklist = implode( "\n", $blacklist ); // Convert back to string.
1761 $settings->save();
1762 }
1763
1764 /**
1765 * Upgrade to 5.2.0.
1766 *
1767 * @return void
1768 */
1769 private function upgrade_5_2_0(): void {
1770 $this->update_ua_blocklist();
1771 // Remove the prev Breadcrumbs.
1772 wd_di()->get( \WP_Defender\Controller\Strong_Password::class )->remove_data();
1773 // Add the "What's new" modal.
1774 update_site_option( Feature_Modal::FEATURE_SLUG, true );
1775 }
1776
1777 /**
1778 * Upgrade to 5.3.0.
1779 *
1780 * @return void
1781 */
1782 private function upgrade_5_3_0(): void {
1783 // Add the "What's new" modal.
1784 update_site_option( Feature_Modal::FEATURE_SLUG, true );
1785 // Add composite index to the defender_lockout table.
1786 global $wpdb;
1787 // Check if the index already exists.
1788 $wpdb->query( "SHOW INDEX FROM {$wpdb->base_prefix}defender_lockout_log WHERE Key_name = 'idx_ip_date'" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1789 if ( 0 === $wpdb->num_rows ) {
1790 // If the index does not exist, create it.
1791 $prev_val = $wpdb->hide_errors();
1792 $wpdb->query( "ALTER TABLE {$wpdb->base_prefix}defender_lockout_log ADD INDEX idx_ip_date (ip, date DESC);" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1793 $wpdb->show_errors( $prev_val );
1794 }
1795 }
1796
1797 /**
1798 * Upgrade to 5.3.1.
1799 *
1800 * @return void
1801 */
1802 private function upgrade_5_3_1(): void {
1803 delete_site_transient( \WP_Defender\Component\IP\Antibot_Global_Firewall::BLOCKLIST_STATS_KEY );
1804 }
1805
1806 /**
1807 * Improve UA Blocklist.
1808 *
1809 * @return void
1810 */
1811 private function improve_ua_blocklist(): void {
1812 $settings = wd_di()->get( User_Agent_Lockout::class );
1813 $blocklist_custom = $settings->get_lockout_list( 'blocklist' );
1814 if ( empty( $blocklist_custom ) ) {
1815 return;
1816 }
1817 // Get 'Blocklist Presets', check and remove duplicates on 'Custom User Agents'.
1818 $blocklist_presets = User_Agent_Service::get_nested_keys_of_blocklist_presets();
1819 $common_result = array_intersect( $blocklist_custom, $blocklist_presets );
1820 if ( ! empty( $common_result ) ) {
1821 $blocklist_custom = User_Agent_Service::check_and_remove_duplicates(
1822 $blocklist_custom,
1823 $common_result
1824 );
1825 // Convert back to string.
1826 $settings->blacklist = implode( PHP_EOL, $blocklist_custom );
1827 // Enable option with nested suboptions.
1828 $settings->blocklist_presets = true;
1829 $settings->blocklist_preset_values = $common_result;
1830 }
1831 // The same, but for 'Script Presets'.
1832 $script_presets = array_keys( User_Agent_Service::get_script_presets() );
1833 $common_result = array_intersect( $blocklist_custom, $script_presets );
1834 if ( ! empty( $common_result ) ) {
1835 $blocklist_custom = User_Agent_Service::check_and_remove_duplicates(
1836 $blocklist_custom,
1837 $common_result
1838 );
1839 // Convert back to string.
1840 $settings->blacklist = implode( PHP_EOL, $blocklist_custom );
1841 // Enable option with nested suboptions.
1842 $settings->script_presets = true;
1843 $settings->script_preset_values = $common_result;
1844 }
1845 $settings->save();
1846 }
1847
1848 /**
1849 * Upgrade to 5.4.0.
1850 *
1851 * @return void
1852 */
1853 private function upgrade_5_4_0(): void {
1854 update_site_option( Feature_Modal::FEATURE_SLUG, true );
1855
1856 $this->improve_ua_blocklist();
1857 }
1858
1859 /**
1860 * Upgrade to 5.5.0.
1861 *
1862 * @return void
1863 */
1864 private function upgrade_5_5_0(): void {
1865 update_site_option( Feature_Modal::FEATURE_SLUG, true );
1866 }
1867
1868 /**
1869 * Change lockout log mentions from fake_bot to malicious_bot. Also move BotTrap settings.
1870 *
1871 * @return void
1872 */
1873 private function change_to_malicious_bot(): void {
1874 global $wpdb;
1875
1876 $table_name = $wpdb->base_prefix . 'defender_lockout_log';
1877
1878 $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1879 $wpdb->prepare(
1880 "UPDATE $table_name SET type = %s, log = %s WHERE type = %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
1881 'malicious_bot',
1882 'Lockout occurred: Bot ignored robots.txt rules.',
1883 'bot_trap'
1884 )
1885 );
1886 // Clear schedule as the name has changed to 'wpdef_rotate_malicious_bot_secret_hash'.
1887 wp_clear_scheduled_hook( 'wpdef_rotate_bot_trap_secret_hash' );
1888 // Move the BotTrap settings to new Malicious Bot settings.
1889 $settings = wd_di()->get( User_Agent_Lockout::class );
1890 if ( isset( $settings->bot_trap_enabled ) ) {
1891 $settings->malicious_bot_enabled = $settings->bot_trap_enabled;
1892 $settings->malicious_bot_lockout_type = $settings->bot_trap_lockout_type;
1893 $settings->malicious_bot_lockout_duration = $settings->bot_trap_lockout_duration;
1894 $settings->malicious_bot_lockout_duration_unit = $settings->bot_trap_lockout_duration_unit;
1895 $settings->save();
1896 }
1897 }
1898
1899 /**
1900 * Upgrade to 5.6.0.
1901 *
1902 * @return void
1903 */
1904 private function upgrade_5_6_0(): void {
1905 $this->change_to_malicious_bot();
1906 // Remove the prev Breadcrumbs.
1907 wd_di()->get( \WP_Defender\Component\Breadcrumbs::class )->delete_previous_meta();
1908 // Add the "What's new" modal.
1909 update_site_option( Feature_Modal::FEATURE_SLUG, true );
1910 }
1911 }
1912