PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.3.0
Defender Security – Malware Scanner, Login Security & Firewall v6.3.0
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
← All changes | src/class-upgrader.php +575 -66 5.4.0 → 6.3.0 View file →
@@ -67,20 +67,20 @@
67 67 public function migrate_security_headers(): void {
68 68 $model = wd_di()->get( Security_Headers::class );
69 69 $option = get_site_option( $model->get_table() );
70 70
71 - if ( empty( $option ) ) {
71 + if ( null === $option || false === $option || '' === $option || '0' === $option || array() === $option ) {
72 72 // Part of Security tweaks data.
73 73 $old_key = 'wd_hardener_settings';
74 74 $old_settings = get_site_option( $old_key );
75 75 if ( ! is_array( $old_settings ) ) {
76 76 $old_settings = json_decode( $old_settings, true );
77 - if ( is_array( $old_settings ) && isset( $old_settings['data'] ) && ! empty( $old_settings['data'] ) ) {
77 + if ( is_array( $old_settings ) && isset( $old_settings['data'] ) && is_array( $old_settings['data'] ) && array() !== $old_settings['data'] ) {
78 78 // Exist 'X-Frame-Options'.
79 - if ( isset( $old_settings['data']['sh_xframe'] ) && ! empty( $old_settings['data']['sh_xframe'] ) ) {
79 + if ( isset( $old_settings['data']['sh_xframe'] ) && is_array( $old_settings['data']['sh_xframe'] ) && array() !== $old_settings['data']['sh_xframe'] ) {
80 80 $header_data = $old_settings['data']['sh_xframe'];
81 81
82 - $mode = ( isset( $header_data['mode'] ) && ! empty( $header_data['mode'] ) )
82 + $mode = ( isset( $header_data['mode'] ) && '' !== trim( $header_data['mode'] ) )
83 83 ? strtolower( $header_data['mode'] )
84 84 : false;
85 85 /**
86 86 * Directive ALLOW-FROM is deprecated. If header directive is ALLOW-FROM then set 'sameorigin'.
@@ -95,13 +95,17 @@
95 95 $model->sh_xframe = true;
96 96 }
97 97
98 98 // Exist 'X-XSS-Protection'.
99 - if ( isset( $old_settings['data']['sh_xss_protection'] ) && ! empty( $old_settings['data']['sh_xss_protection'] ) ) {
99 + if (
100 + isset( $old_settings['data']['sh_xss_protection'] )
101 + && is_array( $old_settings['data']['sh_xss_protection'] )
102 + && array() !== $old_settings['data']['sh_xss_protection']
103 + ) {
100 104 $header_data = $old_settings['data']['sh_xss_protection'];
101 105
102 106 if ( isset( $header_data['mode'] )
103 - && ! empty( $header_data['mode'] )
107 + && '' !== trim( $header_data['mode'] )
104 108 && in_array( $header_data['mode'], array( 'sanitize', 'block' ), true )
105 109 ) {
106 110 $model->sh_xss_protection_mode = $header_data['mode'];
107 111 $model->sh_xss_protection = true;
@@ -108,12 +112,16 @@
108 112 }
109 113 }
110 114
111 115 // Exist 'X-Content-Type-Options'.
112 - if ( isset( $old_settings['data']['sh_content_type_options'] ) && ! empty( $old_settings['data']['sh_content_type_options'] ) ) {
116 + if (
117 + isset( $old_settings['data']['sh_content_type_options'] )
118 + && is_array( $old_settings['data']['sh_content_type_options'] )
119 + && array() !== $old_settings['data']['sh_content_type_options']
120 + ) {
113 121 $header_data = $old_settings['data']['sh_content_type_options'];
114 122
115 - if ( isset( $header_data['mode'] ) && ! empty( $header_data['mode'] ) ) {
123 + if ( isset( $header_data['mode'] ) && '' !== trim( $header_data['mode'] ) ) {
116 124 $model->sh_content_type_options_mode = $header_data['mode'];
117 125 $model->sh_content_type_options = true;
118 126 }
119 127 }
@@ -118,15 +126,27 @@
118 126 }
119 127 }
120 128
121 129 // Exist 'Strict Transport'.
122 - if ( isset( $old_settings['data']['sh_strict_transport'] ) && ! empty( $old_settings['data']['sh_strict_transport'] ) ) {
130 + if (
131 + isset( $old_settings['data']['sh_strict_transport'] )
132 + && is_array( $old_settings['data']['sh_strict_transport'] )
133 + && array() !== $old_settings['data']['sh_strict_transport']
134 + ) {
123 135 $header_data = $old_settings['data']['sh_strict_transport'];
124 136
125 - if ( isset( $header_data['hsts_preload'] ) && ! empty( $header_data['hsts_preload'] ) ) {
126 - $model->hsts_preload = (int) $header_data['hsts_preload'];
137 + $hsts_preload = isset( $header_data['hsts_preload'] ) ? (int) $header_data['hsts_preload'] : 0;
138 + if ( 0 < $hsts_preload ) {
139 + $model->hsts_preload = $hsts_preload;
127 140 }
128 - if ( isset( $header_data['include_subdomain'] ) && ! empty( $header_data['include_subdomain'] ) ) {
141 + if (
142 + isset( $header_data['include_subdomain'] )
143 + && ! is_null( $header_data['include_subdomain'] )
144 + && '' !== $header_data['include_subdomain']
145 + && '0' !== $header_data['include_subdomain']
146 + && false !== $header_data['include_subdomain']
147 + && ( ! is_int( $header_data['include_subdomain'] ) || 0 < $header_data['include_subdomain'] )
148 + ) {
129 149 $model->include_subdomain = in_array(
130 150 $header_data['include_subdomain'],
131 151 array( 'true', '1', 1 ),
132 152 true
@@ -131,19 +151,24 @@
131 151 array( 'true', '1', 1 ),
132 152 true
133 153 ) ? 1 : 0;
134 154 }
135 - if ( isset( $header_data['hsts_cache_duration'] ) && ! empty( $header_data['hsts_cache_duration'] ) ) {
136 - $model->hsts_cache_duration = $header_data['hsts_cache_duration'];
155 + $cache_duration = isset( $header_data['hsts_cache_duration'] ) ? trim( $header_data['hsts_cache_duration'] ) : '';
156 + if ( '' !== $cache_duration ) {
157 + $model->hsts_cache_duration = $cache_duration;
137 158 }
138 159 $model->sh_strict_transport = true;
139 160 }
140 161
141 162 // Exist 'Referrer Policy'.
142 - if ( isset( $old_settings['data']['sh_referrer_policy'] ) && ! empty( $old_settings['data']['sh_referrer_policy'] ) ) {
163 + if (
164 + isset( $old_settings['data']['sh_referrer_policy'] )
165 + && is_array( $old_settings['data']['sh_referrer_policy'] )
166 + && array() !== $old_settings['data']['sh_referrer_policy']
167 + ) {
143 168 $header_data = $old_settings['data']['sh_referrer_policy'];
144 169
145 - if ( isset( $header_data['mode'] ) && ! empty( $header_data['mode'] ) ) {
170 + if ( isset( $header_data['mode'] ) && '' !== trim( $header_data['mode'] ) ) {
146 171 $model->sh_referrer_policy_mode = $header_data['mode'];
147 172 $model->sh_referrer_policy = true;
148 173 }
149 174 }
@@ -148,15 +173,26 @@
148 173 }
149 174 }
150 175
151 176 // Exist 'Feature-Policy'.
152 - if ( isset( $old_settings['data']['sh_feature_policy'] ) && ! empty( $old_settings['data']['sh_feature_policy'] ) ) {
177 + if (
178 + isset( $old_settings['data']['sh_feature_policy'] )
179 + && is_array( $old_settings['data']['sh_feature_policy'] )
180 + && array() !== $old_settings['data']['sh_feature_policy']
181 + ) {
153 182 $header_data = $old_settings['data']['sh_feature_policy'];
154 183
155 - if ( isset( $header_data['mode'] ) && ! empty( $header_data['mode'] ) ) {
184 + if ( isset( $header_data['mode'] ) && '' !== trim( $header_data['mode'] ) ) {
156 185 $mode = strtolower( $header_data['mode'] );
157 186 $model->sh_feature_policy_mode = $mode;
158 - if ( 'origins' === $mode && isset( $header_data['values'] ) && ! empty( $header_data['values'] ) ) {
187 + if (
188 + 'origins' === $mode
189 + && isset( $header_data['values'] )
190 + && (
191 + ( is_array( $header_data['values'] ) && array() !== $header_data['values'] )
192 + || ( is_string( $header_data['values'] ) && '' !== trim( $header_data['values'] ) )
193 + )
194 + ) {
159 195 // The values differ from the values of the 'X-Frame-Options' key, because they may be an array.
160 196 if ( is_array( $header_data['values'] ) ) {
161 197 $model->sh_feature_policy_urls = implode( PHP_EOL, $header_data['values'] );
162 198 // Otherwise.
@@ -209,9 +245,9 @@
209 245 && version_compare( $current_version, '2.4', '<' )
210 246 ) {
211 247 $config_component = wd_di()->get( Backup_Settings::class );
212 248 $prev_data = $config_component->backup_data();
213 - if ( empty( $prev_data ) ) {
249 + if ( ! is_array( $prev_data ) || array() === $prev_data ) {
214 250 return;
215 251 }
216 252 $adapter = wd_di()->get( Config_Adapter::class );
217 253 $migrated_data = $adapter->upgrade( $prev_data );
@@ -219,9 +255,9 @@
219 255 // Hide Onboard page.
220 256 update_site_option( 'wp_defender_shown_activator', true );
221 257
222 258 $configs = $config_component->get_configs();
223 - if ( ! empty( $configs ) ) {
259 + if ( array() !== $configs ) {
224 260 foreach ( $configs as $k => $config ) {
225 261 if (
226 262 $config_component->verify_config_data( $config )
227 263 && ! $config_component->check_for_new_structure( $config['configs'] )
@@ -250,9 +286,9 @@
250 286 private function upgrade_2_4_2(): void {
251 287 // Update Scan settings.
252 288 $model_settings = wd_di()->get( Scan_Settings::class );
253 289 $option = get_site_option( $model_settings->get_table() );
254 - if ( ! empty( $option ) && ! is_array( $option ) ) {
290 + if ( is_string( $option ) && '' !== trim( $option ) ) {
255 291 $old_settings = json_decode( $option, true );
256 292 if ( is_array( $old_settings ) && isset( $old_settings['max_filesize'] ) ) {
257 293 $model_settings->filesize = (int) $old_settings['max_filesize'];
258 294 $model_settings->save();
@@ -259,9 +295,9 @@
259 295 }
260 296 }
261 297 // Update 'reminder_duration' value inside the Security Key tweak.
262 298 $old_settings = get_site_option( 'wd_hardener_settings' );
263 - if ( ! empty( $old_settings ) && ! is_array( $old_settings ) ) {
299 + if ( is_string( $old_settings ) && '' !== trim( $old_settings ) ) {
264 300 $old_settings = json_decode( $old_settings, true );
265 301 $tweak_sec_key = wd_di()->get( Security_Key::class );
266 302
267 303 if ( is_array( $old_settings ) && isset( $old_settings['data']['securityReminderDuration'] )
@@ -283,9 +319,9 @@
283 319 * @since 2.4.7
284 320 */
285 321 private function migrate_scan_integrity_check(): void {
286 322 $model = new Scan_Settings();
287 - $model->check_core = (bool) $model->integrity_check;
323 + $model->check_core = $model->integrity_check;
288 324 $model->save();
289 325 }
290 326
291 327 /**
@@ -300,9 +336,11 @@
300 336 return;
301 337 }
302 338
303 339 $db_version = get_site_option( 'wd_db_version' );
304 - if ( empty( $db_version ) ) {
340 + if ( ! is_string( $db_version ) || '' === $db_version ) {
341 + self::cache_event_time( 'plugin_installed' );
342 + $this->create_database_tables_common();
305 343 update_site_option( 'wd_db_version', DEFENDER_DB_VERSION );
306 344 update_site_option( Feature_Modal::FEATURE_SLUG, true );
307 345
308 346 return;
@@ -308,11 +346,21 @@
308 346 return;
309 347 }
310 348
311 349 if ( DEFENDER_DB_VERSION === $db_version ) {
350 + // Check if we need to create pro tables (Free to Pro upgrade scenario).
351 + if ( class_exists( Bootstrap::class ) && method_exists( Bootstrap::class, 'create_database_tables' ) && false === (bool) get_site_option( 'wd_pro_tables_created', false ) ) {
352 + wd_di()->get( Bootstrap::class )->create_database_tables();
353 + }
312 354 return;
313 355 }
314 - $this->create_database_tables();
356 + // Cache last updated time.
357 + self::cache_event_time( 'plugin_upgraded' );
358 +
359 + $this->create_database_tables_common();
360 + if ( class_exists( Bootstrap::class ) && method_exists( Bootstrap::class, 'create_database_tables' ) ) {
361 + wd_di()->get( Bootstrap::class )->create_database_tables();
362 + }
315 363 $this->maybe_show_new_features( $db_version );
316 364 $this->migrate_configs( $db_version );
317 365
318 366 if ( version_compare( $db_version, '2.2.9', '<' ) ) {
@@ -420,8 +468,26 @@
420 468 }
421 469 if ( version_compare( $db_version, '5.4.0', '<' ) ) {
422 470 $this->upgrade_5_4_0();
423 471 }
472 + if ( version_compare( $db_version, '5.5.0', '<' ) ) {
473 + $this->upgrade_5_5_0();
474 + }
475 + if ( version_compare( $db_version, '5.6.0', '<' ) ) {
476 + $this->upgrade_5_6_0();
477 + }
478 + if ( version_compare( $db_version, '5.7.0', '<' ) ) {
479 + $this->upgrade_5_7_0();
480 + }
481 + if ( version_compare( $db_version, '6.0.0', '<' ) ) {
482 + $this->upgrade_6_0_0();
483 + }
484 + if ( version_compare( $db_version, '6.1.0', '<' ) ) {
485 + $this->upgrade_6_1_0();
486 + }
487 + if ( version_compare( $db_version, '6.2.0', '<' ) ) {
488 + $this->upgrade_6_2_0();
489 + }
424 490 // This is not a new installation. Make a mark.
425 491 defender_no_fresh_install();
426 492 // Don't run any function below this line.
427 493 update_site_option( 'wd_db_version', DEFENDER_DB_VERSION );
@@ -427,8 +493,27 @@
427 493 update_site_option( 'wd_db_version', DEFENDER_DB_VERSION );
428 494 }
429 495
430 496 /**
497 + * Cache the event time.
498 + *
499 + * @param string $event Event key.
500 + */
501 + private static function cache_event_time( $event ) {
502 + $option_key = \WP_Defender\Helper\Analytics\Deactivation_Survey::EVENT_DATA_OPTION;
503 + $event_times = get_site_option( $option_key, array() );
504 + $event_times[ $event ] = time();
505 + update_site_option( $option_key, $event_times );
506 + }
507 +
508 + /**
509 + * Cache the last activated time.
510 + */
511 + public static function date_activated() {
512 + self::cache_event_time( 'plugin_activated' );
513 + }
514 +
515 + /**
431 516 * Index necessary columns.
432 517 * Sometimes this function call twice that's why we have to check index already exists or not.
433 518 * `dbDelta` not work on `ALTER TABLE` query, so we had to use $wpdb->query().
434 519 *
@@ -642,11 +727,11 @@
642 727 */
643 728 private function force_nf_lockout_exclusions(): void {
644 729 $nf_settings = new Notfound_Lockout();
645 730 $allowlist = $nf_settings->get_lockout_list( 'allowlist' );
646 - $default_allowlist = array( '.css', '.js', '.map' );
731 + $default_allowlist = array( '/cdn-cgi/challenge-platform/' );
647 732 $is_save = false;
648 - if ( ! empty( $allowlist ) ) {
733 + if ( array() !== $allowlist ) {
649 734 foreach ( $default_allowlist as $item ) {
650 735 if ( ! in_array( $item, $allowlist, true ) ) {
651 736 $allowlist[] = $item;
652 737 $is_save = true;
@@ -653,9 +738,9 @@
653 738 }
654 739 }
655 740 $nf_settings->whitelist = implode( "\n", $allowlist );
656 741 } else {
657 - $nf_settings->whitelist = ".css\n.js\n.map";
742 + $nf_settings->whitelist = ".css\n.js\n.map\n/cdn-cgi/challenge-platform/";
658 743 $is_save = true;
659 744 }
660 745 // Save it.
661 746 if ( $is_save ) {
@@ -672,9 +757,11 @@
672 757 private function upgrade_2_5_6(): void {
673 758 $adapted_component = wd_di()->get( Legacy_Versions::class );
674 759 $issue_list = $adapted_component->get_scan_issue_data();
675 760 $ignored_list = $adapted_component->get_scan_ignored_data();
676 - if ( ! empty( $issue_list ) || ! empty( $ignored_list ) ) {
761 + if (
762 + ( is_array( $issue_list ) && array() !== $issue_list )
763 + || ( is_array( $ignored_list ) && array() !== $ignored_list ) ) {
677 764 $adapted_component->migrate_scan_data( $issue_list, $ignored_list );
678 765 $adapted_component->remove_old_scan_data( $issue_list, $ignored_list );
679 766 $adapted_component->change_onboarding_status();
680 767 }
@@ -693,18 +780,17 @@
693 780 *
694 781 * @return void
695 782 */
696 783 private function update_scan_error_send_body( $model ): void {
697 - if (
698 - isset( $model->configs['template']['error']['body'] )
699 - && ! empty( $model->configs['template']['error']['body'] )
700 - ) {
784 + $body = isset( $model->configs['template']['error']['body'] ) ? trim( $model->configs['template']['error']['body'] ) : '';
785 +
786 + if ( '' !== $body ) {
701 787 $needle = '{follow this link} and check the logs to see what casued the failure';
702 - if ( false !== stripos( $model->configs['template']['error']['body'], $needle ) ) {
788 + if ( false !== stripos( $body, $needle ) ) {
703 789 $model->configs['template']['error']['body'] = str_replace(
704 790 $needle,
705 791 'visit your site and run a manual scan',
706 - $model->configs['template']['error']['body']
792 + $body
707 793 );
708 794 $model->save();
709 795 }
710 796 }
@@ -719,9 +805,9 @@
719 805 private function upgrade_2_6_1(): void {
720 806 // Update the title of the basic config.
721 807 $config_component = wd_di()->get( Backup_Settings::class );
722 808 $configs = $config_component->get_configs();
723 - if ( ! empty( $configs ) ) {
809 + if ( array() !== $configs ) {
724 810 foreach ( $configs as $k => $config ) {
725 811 if ( 0 === strcmp( $config['name'], esc_html__( 'Basic config', 'defender-security' ) ) ) {
726 812 $config['name'] = esc_html__( 'Basic Config', 'defender-security' );
727 813
@@ -766,12 +852,13 @@
766 852 );
767 853
768 854 foreach ( $models as $model ) {
769 855 $is_updated = false;
770 - if ( ! empty( $model->configs['template']['error']['body'] ) ) {
771 - $subject = $this->replace_scan_email_content_error( $model->configs['template']['error']['body'] );
856 + $body = isset( $model->configs['template']['error']['body'] ) ? trim( $model->configs['template']['error']['body'] ) : '';
857 + if ( '' !== $body ) {
858 + $subject = $this->replace_scan_email_content_error( $body );
772 859
773 - if ( $model->configs['template']['error']['body'] !== $subject ) {
860 + if ( $body !== $subject ) {
774 861 $is_updated = true;
775 862
776 863 $model->configs['template']['error']['body'] = $subject;
777 864 }
@@ -776,12 +863,13 @@
776 863 $model->configs['template']['error']['body'] = $subject;
777 864 }
778 865 }
779 866
780 - if ( ! empty( $model->configs['template']['found']['body'] ) ) {
781 - $subject = $this->replace_scan_email_content_issue_found( $model->configs['template']['found']['body'] );
867 + $body = isset( $model->configs['template']['found']['body'] ) ? trim( $model->configs['template']['found']['body'] ) : '';
868 + if ( '' !== $body ) {
869 + $subject = $this->replace_scan_email_content_issue_found( $body );
782 870
783 - if ( $model->configs['template']['found']['body'] !== $subject ) {
871 + if ( $body !== $subject ) {
784 872 $is_updated = true;
785 873
786 874 $model->configs['template']['found']['body'] = $subject;
787 875 }
@@ -786,12 +874,13 @@
786 874 $model->configs['template']['found']['body'] = $subject;
787 875 }
788 876 }
789 877
790 - if ( ! empty( $model->configs['template']['not_found']['body'] ) ) {
791 - $subject = $this->replace_scan_email_content_issue_not_found( $model->configs['template']['not_found']['body'] );
878 + $body = isset( $model->configs['template']['not_found']['body'] ) ? trim( $model->configs['template']['not_found']['body'] ) : '';
879 + if ( '' !== $body ) {
880 + $subject = $this->replace_scan_email_content_issue_not_found( $body );
792 881
793 - if ( $model->configs['template']['not_found']['body'] !== $subject ) {
882 + if ( $body !== $subject ) {
794 883 $is_updated = true;
795 884
796 885 $model->configs['template']['not_found']['body'] = $subject;
797 886 }
@@ -829,9 +918,9 @@
829 918 // Migrate data from Malware_Report to Scan settings.
830 919 $scan_settings->scheduled_scanning = Notification::STATUS_ACTIVE === $malware_report->status;
831 920 $scan_settings->frequency = $malware_report->frequency;
832 921 $scan_settings->day = $malware_report->day;
833 - $scan_settings->day_n = $malware_report->day_n;
922 + $scan_settings->day_n = (int) $malware_report->day_n;
834 923 $scan_settings->time = $malware_report->time;
835 924 $scan_settings->save();
836 925
837 926 $this->update_2fa_send_body();
@@ -854,13 +943,13 @@
854 943 wd_di()->get( Tweak_Reminder::class ),
855 944 );
856 945
857 946 foreach ( $models as $model ) {
858 - if ( ! empty( $model->in_house_recipients ) && is_array( $model->in_house_recipients ) ) {
947 + if ( is_array( $model->in_house_recipients ) && array() !== $model->in_house_recipients ) {
859 948 $is_updated = false;
860 949
861 950 foreach ( $model->in_house_recipients as &$recipient ) {
862 - if ( empty( $recipient['role'] ) ) {
951 + if ( ! isset( $recipient['role'] ) || ! is_string( $recipient['role'] ) || '' === trim( $recipient['role'] ) ) {
863 952 $is_updated = true;
864 953 $recipient['role'] = $this->get_current_user_role( $recipient['id'] );
865 954 }
866 955 }
@@ -983,9 +1072,9 @@
983 1072 */
984 1073 private function upgrade_3_2_0(): void {
985 1074 $model = wd_di()->get( Two_Fa_Settings::class );
986 1075
987 - if ( ! empty( $model->app_title ) ) {
1076 + if ( is_string( $model->app_title ) && '' !== $model->app_title ) {
988 1077 $model->app_title = wp_specialchars_decode( $model->app_title, ENT_QUOTES );
989 1078 }
990 1079
991 1080 $model->custom_graphic_type = Two_Fa_Settings::CUSTOM_GRAPHIC_TYPE_UPLOAD;
@@ -1019,9 +1108,9 @@
1019 1108 $blogs = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1020 1109 $wpdb->prepare( "SELECT blog_id FROM {$wpdb->blogs} LIMIT %d, %d", $offset, $limit ),
1021 1110 ARRAY_A
1022 1111 );
1023 - while ( ! empty( $blogs ) && is_array( $blogs ) ) {
1112 + while ( is_array( $blogs ) && array() !== $blogs ) {
1024 1113 foreach ( $blogs as $blog ) {
1025 1114 switch_to_blog( $blog['blog_id'] );
1026 1115
1027 1116 $this->update_webauthn_user_handle_core( $service );
@@ -1057,9 +1146,9 @@
1057 1146
1058 1147 $users = get_users( array( 'fields' => array( 'ID', 'user_login', 'display_name' ) ) );
1059 1148
1060 1149 foreach ( $users as $user ) {
1061 - if ( empty( $data ) ) {
1150 + if ( ! is_array( $data ) || array() === $data ) {
1062 1151 break;
1063 1152 }
1064 1153
1065 1154 $user_credentials = array();
@@ -1083,9 +1172,9 @@
1083 1172 unset( $data[ $key ] );
1084 1173 }
1085 1174 }
1086 1175
1087 - if ( ! empty( $user_credentials ) ) {
1176 + if ( array() !== $user_credentials ) {
1088 1177 $service->setCredentials( (int) $user->ID, $user_credentials );
1089 1178 }
1090 1179 }
1091 1180 }
@@ -1145,9 +1234,9 @@
1145 1234 if ( $query->get_total() > 0 ) {
1146 1235 // Hashed codes.
1147 1236 foreach ( $query->get_results() as $user_id ) {
1148 1237 $backup_code = get_user_meta( $user_id, Fallback_Email::FALLBACK_BACKUP_CODE_KEY, true );
1149 - if ( ! empty( $backup_code ) && isset( $backup_code['code'], $backup_code['time'] ) ) {
1238 + if ( isset( $backup_code['code'], $backup_code['time'] ) ) {
1150 1239 update_user_meta(
1151 1240 $user_id,
1152 1241 Fallback_Email::FALLBACK_BACKUP_CODE_KEY,
1153 1242 array(
@@ -1299,9 +1388,8 @@
1299 1388 */
1300 1389 private function update_malware_notification_setting(): void {
1301 1390 $models = array(
1302 1391 wd_di()->get( Malware_Notification::class ),
1303 - wd_di()->get( Malware_Report::class ),
1304 1392 );
1305 1393
1306 1394 foreach ( $models as $model ) {
1307 1395 $is_updated = false;
@@ -1455,9 +1543,9 @@
1455 1543 if ( $prev_name !== $new_name ) {
1456 1544 $is_updated = true;
1457 1545 }
1458 1546 $prev_desc = $config['description'];
1459 - $new_desc = empty( $config['description'] ) ? '' : sanitize_textarea_field( $config['description'] );
1547 + $new_desc = ! is_string( $config['description'] ) || '' === trim( $config['description'] ) ? '' : sanitize_textarea_field( $config['description'] );
1460 1548 if ( $prev_desc !== $new_desc ) {
1461 1549 $is_updated = true;
1462 1550 }
1463 1551 }
@@ -1483,10 +1571,8 @@
1483 1571 // Changes for Lockout table.
1484 1572 $wpdb->query( "ALTER TABLE{$wpdb->base_prefix}defender_lockout MODIFY COLUMN ip VARCHAR(45)" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1485 1573 // Changes for Lockout log table.
1486 1574 $wpdb->query( "ALTER TABLE{$wpdb->base_prefix}defender_lockout_log MODIFY COLUMN ip VARCHAR(45)" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1487 - // Changes for Audit log table.
1488 - $wpdb->query( "ALTER TABLE{$wpdb->base_prefix}defender_audit_log MODIFY COLUMN ip VARCHAR(45)" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1489 1575 $wpdb->show_errors( $prev_val );
1490 1576 }
1491 1577
1492 1578 /**
@@ -1496,9 +1582,9 @@
1496 1582 */
1497 1583 private function upgrade_3_11_0(): void {
1498 1584 // Move Global IP settings.
1499 1585 $option = get_site_option( wd_di()->get( Model_Blacklist_Lockout::class )->get_table() );
1500 - if ( ! empty( $option ) && is_string( $option ) ) {
1586 + if ( is_string( $option ) && '' !== trim( $option ) ) {
1501 1587 $old_settings = json_decode( $option, true );
1502 1588 if ( is_array( $old_settings ) && isset( $old_settings['global_ip_list'] ) ) {
1503 1589 $model_global_ip = wd_di()->get( Global_Ip_Lockout::class );
1504 1590 $model_global_ip->enabled = (bool) $old_settings['global_ip_list'];
@@ -1581,9 +1667,9 @@
1581 1667 $limit
1582 1668 ),
1583 1669 ARRAY_A
1584 1670 );
1585 - while ( ! empty( $blogs ) && is_array( $blogs ) ) {
1671 + while ( is_array( $blogs ) && array() !== $blogs ) {
1586 1672 foreach ( $blogs as $blog ) {
1587 1673 switch_to_blog( $blog['blog_id'] );
1588 1674
1589 1675 $maxmind_dir = $this->get_tmp_path() . DIRECTORY_SEPARATOR . MaxMind_Geolocation::DB_DIRECTORY;
@@ -1741,9 +1827,9 @@
1741 1827 */
1742 1828 private function update_ua_blocklist(): void {
1743 1829 $settings = wd_di()->get( User_Agent_Lockout::class );
1744 1830 $blacklist = $settings->get_lockout_list( 'blocklist', false );
1745 - if ( empty( $blacklist ) ) {
1831 + if ( array() === $blacklist ) {
1746 1832 return;
1747 1833 }
1748 1834 $blacklist = array_filter(
1749 1835 $blacklist,
@@ -1761,10 +1847,8 @@
1761 1847 * @return void
1762 1848 */
1763 1849 private function upgrade_5_2_0(): void {
1764 1850 $this->update_ua_blocklist();
1765 - // Remove the prev Breadcrumbs.
1766 - wd_di()->get( \WP_Defender\Controller\Strong_Password::class )->remove_data();
1767 1851 // Add the "What's new" modal.
1768 1852 update_site_option( Feature_Modal::FEATURE_SLUG, true );
1769 1853 }
1770 1854
@@ -1804,15 +1888,15 @@
1804 1888 */
1805 1889 private function improve_ua_blocklist(): void {
1806 1890 $settings = wd_di()->get( User_Agent_Lockout::class );
1807 1891 $blocklist_custom = $settings->get_lockout_list( 'blocklist' );
1808 - if ( empty( $blocklist_custom ) ) {
1892 + if ( array() === $blocklist_custom ) {
1809 1893 return;
1810 1894 }
1811 1895 // Get 'Blocklist Presets', check and remove duplicates on 'Custom User Agents'.
1812 1896 $blocklist_presets = User_Agent_Service::get_nested_keys_of_blocklist_presets();
1813 1897 $common_result = array_intersect( $blocklist_custom, $blocklist_presets );
1814 - if ( ! empty( $common_result ) ) {
1898 + if ( array() !== $common_result ) {
1815 1899 $blocklist_custom = User_Agent_Service::check_and_remove_duplicates(
1816 1900 $blocklist_custom,
1817 1901 $common_result
1818 1902 );
@@ -1824,9 +1908,9 @@
1824 1908 }
1825 1909 // The same, but for 'Script Presets'.
1826 1910 $script_presets = array_keys( User_Agent_Service::get_script_presets() );
1827 1911 $common_result = array_intersect( $blocklist_custom, $script_presets );
1828 - if ( ! empty( $common_result ) ) {
1912 + if ( array() !== $common_result ) {
1829 1913 $blocklist_custom = User_Agent_Service::check_and_remove_duplicates(
1830 1914 $blocklist_custom,
1831 1915 $common_result
1832 1916 );
@@ -1847,6 +1931,431 @@
1847 1931 private function upgrade_5_4_0(): void {
1848 1932 update_site_option( Feature_Modal::FEATURE_SLUG, true );
1849 1933
1850 1934 $this->improve_ua_blocklist();
1935 + }
1936 +
1937 + /**
1938 + * Upgrade to 5.5.0.
1939 + *
1940 + * @return void
1941 + */
1942 + private function upgrade_5_5_0(): void {
1943 + update_site_option( Feature_Modal::FEATURE_SLUG, true );
1944 + }
1945 +
1946 + /**
1947 + * Change lockout log mentions from fake_bot to malicious_bot. Also move BotTrap settings.
1948 + *
1949 + * @return void
1950 + */
1951 + private function change_to_malicious_bot(): void {
1952 + global $wpdb;
1953 +
1954 + $table_name = $wpdb->base_prefix . 'defender_lockout_log';
1955 +
1956 + $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1957 + $wpdb->prepare(
1958 + "UPDATE $table_name SET type = %s, log = %s WHERE type = %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
1959 + 'malicious_bot',
1960 + 'Lockout occurred: Bot ignored robots.txt rules.',
1961 + 'bot_trap'
1962 + )
1963 + );
1964 + // Clear schedule as the name has changed to 'wpdef_rotate_malicious_bot_secret_hash'.
1965 + wp_clear_scheduled_hook( 'wpdef_rotate_bot_trap_secret_hash' );
1966 + // Move the BotTrap settings to new Malicious Bot settings.
1967 + $settings = wd_di()->get( User_Agent_Lockout::class );
1968 + if ( isset( $settings->bot_trap_enabled ) ) {
1969 + $settings->malicious_bot_enabled = $settings->bot_trap_enabled;
1970 + $settings->malicious_bot_lockout_type = $settings->bot_trap_lockout_type;
1971 + $settings->malicious_bot_lockout_duration = $settings->bot_trap_lockout_duration;
1972 + $settings->malicious_bot_lockout_duration_unit = $settings->bot_trap_lockout_duration_unit;
1973 + $settings->save();
1974 + }
1975 + }
1976 +
1977 + /**
1978 + * Upgrade to 5.6.0.
1979 + *
1980 + * @return void
1981 + */
1982 + private function upgrade_5_6_0(): void {
1983 + $this->change_to_malicious_bot();
1984 + // Add the "What's new" modal.
1985 + update_site_option( Feature_Modal::FEATURE_SLUG, true );
1986 + }
1987 +
1988 + /**
1989 + * Upgrade to 5.7.0.
1990 + *
1991 + * @return void
1992 + */
1993 + private function upgrade_5_7_0(): void {
1994 + // Remove old objects from ncm options.
1995 + wd_di()->get( \WP_Defender\Component\Network_Cron_Manager::class )->remove_data();
1996 + // Add the "What's new" modal.
1997 + update_site_option( Feature_Modal::FEATURE_SLUG, true );
1998 + // Add Cloudflare challenge-platform to 404 whitelist.
1999 + $this->force_nf_lockout_exclusions();
2000 + }
2001 +
2002 + /**
2003 + * Migrates the uninstall settings from v5.11 to v6.0.
2004 + *
2005 + * During the upgrade:
2006 + * - If at least one of the three options is set to Preserve/Keep,
2007 + * all three options are set to Preserve/Keep.
2008 + */
2009 + private function combine_all_preserve_settings_to_one() {
2010 + $main_settings = wd_di()->get( \WP_Defender\Model\Setting\Main_Setting::class );
2011 +
2012 + if (
2013 + 'preserve' === $main_settings->uninstall_settings ||
2014 + 'keep' === $main_settings->uninstall_data ||
2015 + 'keep' === $main_settings->uninstall_quarantine
2016 + ) {
2017 + $main_settings->uninstall_settings = 'preserve';
2018 + $main_settings->uninstall_data = 'keep';
2019 + $main_settings->uninstall_quarantine = 'keep';
2020 + $main_settings->save();
2021 + }
2022 + }
2023 +
2024 + /**
2025 + * Since with version 6.0.0, two nested options:
2026 + * - Scan core files
2027 + * - Scan plugin files,
2028 + * are hidden, and automatically take the value of the parent "File change detection" option.
2029 + */
2030 + private function combine_all_file_change_detection_settings_to_one() {
2031 + $scan_settings = wd_di()->get( Scan_Settings::class );
2032 + if ( $scan_settings->integrity_check ) {
2033 + $scan_settings->check_core = true;
2034 + $scan_settings->check_plugins = true;
2035 + } else {
2036 + $scan_settings->check_core = false;
2037 + $scan_settings->check_plugins = false;
2038 + }
2039 + $scan_settings->save();
2040 + }
2041 +
2042 + /**
2043 + * Upgrade to 6.0.0: Ensure quarantine table exists for Free HC users and other changes.
2044 + * The quarantine feature is now available in the free version.
2045 + *
2046 + * @return void
2047 + */
2048 + private function upgrade_6_0_0(): void {
2049 + $bootstrap = wd_di()->get( Bootstrap::class );
2050 + $bootstrap->create_table_quarantine();
2051 + $this->combine_all_preserve_settings_to_one();
2052 + $this->combine_all_file_change_detection_settings_to_one();
2053 + $this->update_issues_report_email_template();
2054 + $this->fix_scheduled_scanning_left_disabled();
2055 + $this->migrate_tweak_reminder_schedule();
2056 + $this->migrate_two_fa_force_auth_roles();
2057 + // Add the "What's new" modal only when landing on the exact release it belongs to,
2058 + // so it does not persist into later patch/minor releases (e.g. 6.0.1, 6.1).
2059 + Feature_Modal::maybe_enable_welcome_modal();
2060 + }
2061 +
2062 + /**
2063 + * Upgrade to 6.1.0: Fix Tweak Reminder est_timestamp left as 0 by the 6.0.0 migration.
2064 + * Also sync all report schedules to match Malware Report.
2065 + *
2066 + * @return void
2067 + */
2068 + private function upgrade_6_1_0(): void {
2069 + $model = wd_di()->get( Tweak_Reminder::class );
2070 + if ( 'report' === $model->type && (int) $model->est_timestamp <= 0 ) {
2071 + // Calling save() recovers the correct state: last_sent is
2072 + // reset to now if it is 0, then est_timestamp is recomputed via get_next_run().
2073 + $model->save();
2074 + }
2075 +
2076 + $this->sync_all_report_schedules();
2077 + }
2078 +
2079 + /**
2080 + * Upgrade to 6.2.0.
2081 + *
2082 + * @return void
2083 + */
2084 + private function upgrade_6_2_0(): void {
2085 + $this->fix_notification_recipients_format();
2086 + $this->cancel_recipients_of_inactive_reports();
2087 + }
2088 +
2089 + /**
2090 + * Unsubscribe every recipient of each inactive report so their stored preferences match
2091 + * the reports' last saved on/off state.
2092 + *
2093 + * @return void
2094 + */
2095 + private function cancel_recipients_of_inactive_reports(): void {
2096 + $report_classes = array(
2097 + Tweak_Reminder::class,
2098 + Malware_Report::class,
2099 + Firewall_Report::class,
2100 + Audit_Report::class,
2101 + );
2102 +
2103 + foreach ( $report_classes as $report_class ) {
2104 + $model = wd_di()->get( $report_class );
2105 + if ( $model->check_active_status() ) {
2106 + continue;
2107 + }
2108 +
2109 + $changed = false;
2110 + foreach ( array( 'in_house_recipients', 'out_house_recipients' ) as $group ) {
2111 + $recipients = 'in_house_recipients' === $group
2112 + ? $model->in_house_recipients
2113 + : $model->out_house_recipients;
2114 + if ( ! is_array( $recipients ) ) {
2115 + continue;
2116 + }
2117 +
2118 + foreach ( $recipients as $key => $recipient ) {
2119 + if ( Notification::USER_SUBSCRIBE_CANCELED !== ( $recipient['status'] ?? '' ) ) {
2120 + $recipients[ $key ]['status'] = Notification::USER_SUBSCRIBE_CANCELED;
2121 + $changed = true;
2122 + }
2123 + }
2124 +
2125 + if ( 'in_house_recipients' === $group ) {
2126 + $model->in_house_recipients = $recipients;
2127 + } else {
2128 + $model->out_house_recipients = $recipients;
2129 + }
2130 + }
2131 +
2132 + if ( $changed ) {
2133 + $model->save();
2134 + }
2135 + }
2136 + }
2137 +
2138 + /**
2139 + * Heal recipients that older 6.x versions stored as email-keyed objects instead of arrays,
2140 + * which breaks Defender 5 on downgrade.
2141 + *
2142 + * @return void
2143 + */
2144 + private function fix_notification_recipients_format(): void {
2145 + $tables = array(
2146 + wd_di()->get( Audit_Report::class )->get_table(),
2147 + wd_di()->get( Firewall_Notification::class )->get_table(),
2148 + wd_di()->get( Firewall_Report::class )->get_table(),
2149 + wd_di()->get( Malware_Notification::class )->get_table(),
2150 + wd_di()->get( Malware_Report::class )->get_table(),
2151 + wd_di()->get( Tweak_Reminder::class )->get_table(),
2152 + );
2153 +
2154 + foreach ( $tables as $table ) {
2155 + $raw = get_site_option( $table );
2156 + if ( false === $raw ) {
2157 + continue;
2158 + }
2159 +
2160 + $data = is_array( $raw ) ? $raw : json_decode( $raw, true );
2161 + if ( ! is_array( $data ) ) {
2162 + continue;
2163 + }
2164 +
2165 + $changed = false;
2166 + foreach ( array( 'in_house_recipients', 'out_house_recipients', 'all_subscribers' ) as $key ) {
2167 + if ( isset( $data[ $key ] ) && is_array( $data[ $key ] ) ) {
2168 + // array_values() leaves a proper list untouched but reindexes an email-keyed one.
2169 + $reindexed = array_values( $data[ $key ] );
2170 + if ( $reindexed !== $data[ $key ] ) {
2171 + $data[ $key ] = $reindexed;
2172 + $changed = true;
2173 + }
2174 + }
2175 + }
2176 +
2177 + if ( $changed ) {
2178 + update_site_option( $table, wp_json_encode( $data ) );
2179 + }
2180 + }
2181 + }
2182 +
2183 + /**
2184 + * Sync all report schedules to match Malware Report.
2185 + *
2186 + * Since 6.0.0 the UI enforces a single shared schedule for all reports via sync_report_schedule.
2187 + * Sites upgrading from older versions may have each report on a different schedule.
2188 + * This migration copies Malware_Report's schedule to Scan_Settings (the UI master) and to
2189 + * all four report models, then recomputes est_timestamp on each so all report emails
2190 + * arrive at the same time.
2191 + *
2192 + * @return void
2193 + */
2194 + private function sync_all_report_schedules(): void {
2195 + // Get the schedule from Malware_Report.
2196 + $malware_report = wd_di()->get( Malware_Report::class );
2197 + $frequency = $malware_report->frequency;
2198 + $day = $malware_report->day;
2199 + $day_n = (int) $malware_report->day_n;
2200 + $time = $malware_report->time;
2201 +
2202 + // Update Scan_Settings so the schedule pill shows the correct label.
2203 + $scan_settings = wd_di()->get( Scan_Settings::class );
2204 + $scan_settings->frequency = $frequency;
2205 + $scan_settings->day = $day;
2206 + $scan_settings->day_n = $day_n;
2207 + $scan_settings->time = $time;
2208 + $scan_settings->save();
2209 +
2210 + // Apply the same schedule to all report models and recompute est_timestamp
2211 + // so every report fires at the same next occurrence.
2212 + $report_models = array(
2213 + $malware_report,
2214 + wd_di()->get( Firewall_Report::class ),
2215 + wd_di()->get( Audit_Report::class ),
2216 + wd_di()->get( Tweak_Reminder::class ),
2217 + );
2218 +
2219 + foreach ( $report_models as $model ) {
2220 + $model->frequency = $frequency;
2221 + $model->day = $day;
2222 + $model->day_n = $day_n;
2223 + $model->time = $time;
2224 +
2225 + $next_run = $model->get_next_run();
2226 + if ( $next_run instanceof \DateTime ) {
2227 + $model->est_timestamp = $next_run->getTimestamp();
2228 + }
2229 +
2230 + $model->save();
2231 + }
2232 +
2233 + // Show a one-time dashboard bubble so users know about the shared schedule.
2234 + update_site_option( \WP_Defender\Controller\Dashboard::REPORT_SCHEDULE_NOTICE_OPTION, true );
2235 + }
2236 +
2237 + /**
2238 + * Heal 'Scheduled Scanning' left disabled while its related report stayed active.
2239 + *
2240 + * The bundled "Basic Config" preset stored 'scheduled_scanning' out of sync with its
2241 + * 'report' flag, so applying it left scans disabled. Corrects the live setting and any
2242 + * already-stored config presets with the same mismatch.
2243 + *
2244 + * @return void
2245 + */
2246 + private function fix_scheduled_scanning_left_disabled(): void {
2247 + $scan_settings = wd_di()->get( Scan_Settings::class );
2248 + $malware_report = wd_di()->get( Malware_Report::class );
2249 + if ( ! $scan_settings->scheduled_scanning && Notification::STATUS_ACTIVE === $malware_report->status ) {
2250 + $scan_settings->scheduled_scanning = true;
2251 + $scan_settings->save();
2252 + }
2253 +
2254 + $service = wd_di()->get( Backup_Settings::class );
2255 + $configs = Config_Hub_Helper::get_configs( $service );
2256 +
2257 + foreach ( $configs as $key => $config ) {
2258 + $scan = $config['configs']['scan'] ?? null;
2259 + if ( ! is_array( $scan ) || ! isset( $scan['report'], $scan['scheduled_scanning'] ) ) {
2260 + continue;
2261 + }
2262 +
2263 + if ( 'enabled' === $scan['report'] && false === $scan['scheduled_scanning'] ) {
2264 + $configs[ $key ]['configs']['scan']['scheduled_scanning'] = true;
2265 + update_site_option( $key, $configs[ $key ] );
2266 + Config_Hub_Helper::update_on_hub( $configs[ $key ] );
2267 + }
2268 + }
2269 +
2270 + delete_site_transient( Config_Hub_Helper::CONFIGS_TRANSIENT_KEY );
2271 + }
2272 +
2273 + /**
2274 + * Clear force_auth_roles when user_roles is empty.
2275 + *
2276 + * @return void
2277 + */
2278 + private function migrate_two_fa_force_auth_roles(): void {
2279 + $model = wd_di()->get( Two_Fa_Settings::class );
2280 + if ( count( $model->user_roles ) === 0 ) {
2281 + $model->force_auth = false;
2282 + $model->force_auth_roles = array();
2283 + $model->save();
2284 + }
2285 + }
2286 +
2287 + /**
2288 + * Migrate the Hardening report (Tweak Reminder) schedule from 5.11 to 6.0.0.
2289 + *
2290 + * In 5.11, this report was saved with type 'notification' and its frequency was kept in
2291 + * `configs['reminder']`. In 6.0.0, it's saved with type 'report' and uses new, dedicated
2292 + * fields instead: `frequency`, `day`, `day_n`, `time`, `est_timestamp`.
2293 + *
2294 + * Without this migration, a site updated from 5.11 keeps the old type 'notification', so the
2295 + * new scheduling code can't calculate a valid next-send date and the report email gets sent
2296 + * on every cron run instead of on the configured schedule.
2297 + *
2298 + * @return void
2299 + */
2300 + private function migrate_tweak_reminder_schedule(): void {
2301 + $model = wd_di()->get( Tweak_Reminder::class );
2302 + if ( 'notification' !== $model->type ) {
2303 + return;
2304 + }
2305 +
2306 + $model->type = 'report';
2307 +
2308 + $valid_frequencies = array( 'daily', 'weekly', 'monthly' );
2309 + $reminder = $model->configs['reminder'] ?? '';
2310 + $model->frequency = in_array( $reminder, $valid_frequencies, true ) ? $reminder : 'weekly';
2311 +
2312 + if ( null === $model->day || '' === $model->day ) {
2313 + $model->day = 'sunday';
2314 + }
2315 + if ( null === $model->time || '' === $model->time ) {
2316 + $model->time = '4:00';
2317 + }
2318 + if ( ! is_int( $model->day_n ) || $model->day_n < 1 ) {
2319 + $model->day_n = 1;
2320 + }
2321 + // Compute est_timestamp.
2322 + $next_run = $model->get_next_run();
2323 + if ( $next_run instanceof \DateTime ) {
2324 + $model->est_timestamp = $next_run->getTimestamp();
2325 + }
2326 + // Clear the old notification style configs key.
2327 + unset( $model->configs['reminder'] );
2328 + $model->save();
2329 + }
2330 +
2331 + /**
2332 + * Update Issues Report email subject and body to match the new copy.
2333 + *
2334 + * @return void
2335 + */
2336 + private function update_issues_report_email_template(): void {
2337 + $models = array(
2338 + wd_di()->get( Malware_Notification::class ),
2339 + wd_di()->get( Malware_Report::class ),
2340 + );
2341 +
2342 + $default_subject = esc_html__( 'Issues Report for {SITE_URL}: {ISSUES_COUNT} issue(s) found.', 'defender-security' );
2343 + $default_body = esc_html__(
2344 + 'Hi {USER_NAME},
2345 +
2346 +A scan of {SITE_URL} identified {ISSUES_COUNT} issue(s). The issue(s) found is/are listed below.
2347 +
2348 +{ISSUES_LIST}',
2349 + 'defender-security'
2350 + );
2351 +
2352 + foreach ( $models as $model ) {
2353 + if ( ! isset( $model->configs['template']['found'] ) ) {
2354 + continue;
2355 + }
2356 + $model->configs['template']['found']['subject'] = $default_subject;
2357 + $model->configs['template']['found']['body'] = $default_body;
2358 + $model->save();
2359 + }
1851 2360 }
1852 2361 }