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

main-setting.php in Defender Security – Malware Scanner, Login Security & Firewall 3.1.0, at src/controller/main-setting.php

754 lines 19.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WP_Defender\Controller;
4
5 use Calotes\Component\Request;
6 use Calotes\Component\Response;
7 use WP_Defender\Controller;
8 use WP_Defender\Component\Config\Config_Hub_Helper;
9
10 class Main_Setting extends Controller {
11
12 public $slug = 'wdf-setting';
13
14 /**
15 * Use for cache.
16 * @var \WP_Defender\Model\Setting\Main_Setting
17 */
18 public $model;
19
20 /**
21 * @var \WP_Defender\Component\Backup_Settings
22 */
23 protected $service;
24
25 public function __construct() {
26 $this->register_page(
27 esc_html__( 'Settings', 'wpdef' ),
28 $this->slug,
29 array(
30 &$this,
31 'main_view',
32 ),
33 $this->parent_slug
34 );
35
36 // Internal cache.
37 $this->model = new \WP_Defender\Model\Setting\Main_Setting();
38 $this->service = wd_di()->get( \WP_Defender\Component\Backup_Settings::class );
39 add_action( 'defender_enqueue_assets', array( &$this, 'enqueue_assets' ) );
40 $this->register_routes();
41
42 Config_Hub_Helper::clear_config_transient();
43
44 // Add cron schedule to clean out outdated logs.
45 add_action( 'wp_defender_clear_logs', array( $this, 'clear_logs' ) );
46 add_action( 'admin_init', array( $this, 'check_cron_schedule' ) );
47 }
48
49 /**
50 * Safe way to get cached model.
51 *
52 * @return \WP_Defender\Model\Setting\Main_Setting
53 */
54 private function get_model() {
55 if ( is_object( $this->model ) ) {
56 return $this->model;
57 }
58
59 return wd_di()->get( \WP_Defender\Model\Setting\Main_Setting::class );
60 }
61
62 public function enqueue_assets() {
63 if ( ! $this->is_page_active() ) {
64 return;
65 }
66 wp_localize_script( 'def-settings', 'settings', $this->data_frontend() );
67 wp_enqueue_script( 'def-settings' );
68 $this->enqueue_main_assets();
69 }
70
71 /**
72 * Render the root element for frontend.
73 */
74 public function main_view() {
75 $this->render( 'main' );
76 }
77
78 /**
79 * Store settings into db.
80 * @param Request $request
81 *
82 * @return Response
83 *
84 * @defender_route
85 */
86 public function save_settings( Request $request ) {
87 $model = $this->get_model();
88 $data = $request->get_data();
89
90 $model->import( $data );
91 if ( $model->validate() ) {
92 $model->save();
93
94 return new Response(
95 true,
96 array(
97 'message' => __( 'Your settings have been updated.', 'wpdef' ),
98 )
99 );
100 }
101
102 return new Response(
103 false,
104 array(
105 'message' => $model->get_formatted_errors(),
106 )
107 );
108 }
109
110 /**
111 * Reset settings.
112 * @param Request $request
113 *
114 * @return Response
115 * @defender_route
116 */
117 public function reset_settings( Request $request ) {
118 wd_di()->get( \WP_Defender\Controller\Advanced_Tools::class )->remove_settings();
119 wd_di()->get( \WP_Defender\Controller\Audit_Logging::class )->remove_settings();
120 wd_di()->get( \WP_Defender\Controller\Dashboard::class )->remove_settings();
121 wd_di()->get( \WP_Defender\Controller\Security_Tweaks::class )->remove_settings();
122 wd_di()->get( \WP_Defender\Controller\Scan::class )->remove_settings();
123 wd_di()->get( \WP_Defender\Controller\Firewall::class )->remove_settings();
124 wd_di()->get( \WP_Defender\Controller\Firewall_Logs::class )->remove_settings();
125 wd_di()->get( \WP_Defender\Controller\Login_Lockout::class )->remove_settings();
126 wd_di()->get( \WP_Defender\Controller\Nf_Lockout::class )->remove_settings();
127 wd_di()->get( \WP_Defender\Controller\Mask_Login::class )->remove_settings();
128 wd_di()->get( \WP_Defender\Controller\Notification::class )->remove_settings();
129 wd_di()->get( \WP_Defender\Controller\Tutorial::class )->remove_settings();
130 wd_di()->get( \WP_Defender\Controller\Two_Factor::class )->remove_settings();
131 wd_di()->get( \WP_Defender\Controller\Blocklist_Monitor::class )->remove_settings();
132 $this->remove_settings();
133 // Indicate that it is not a new installation.
134 defender_no_fresh_install();
135
136 return new Response(
137 true,
138 array(
139 'message' => __( 'Your settings have been reset.', 'wpdef' ),
140 'redirect' => network_admin_url( 'admin.php?page=wp-defender' ),
141 'interval' => 1,
142 )
143 );
144 }
145
146 public function remove_settings() {
147 wd_di()->get( \WP_Defender\Model\Setting\Main_Setting::class )->delete();
148 }
149
150 public function remove_data() {}
151
152 public function data_frontend() {
153 $model = $this->get_model();
154
155 $this->service->maybe_create_default_config();
156 $configs = $this->get_configs_and_update_status();
157
158 foreach ( $configs as &$config ) {
159 // Unset the data as we don't need it.
160 if ( isset( $config['configs'] ) ) {
161 unset( $config['configs'] );
162 }
163 }
164
165 $link = ( new \WP_Defender\Behavior\WPMUDEV() )->is_member()
166 ? 'https://wpmudev.com/translate/projects/wpdef/'
167 : 'https://translate.wordpress.org/projects/wp-plugins/defender-security/';
168
169 return array_merge(
170 array(
171 'general' => array(
172 'translate' => $model->translate,
173 'usage_tracking' => $model->usage_tracking,
174 'translation_link' => $link,
175 ),
176 'data_settings' => array(
177 'uninstall_settings' => $model->uninstall_settings,
178 'uninstall_data' => $model->uninstall_data,
179 ),
180 'accessibility' => array(
181 'high_contrast_mode' => $model->high_contrast_mode,
182 ),
183 'misc' => array(
184 'setting_url' => network_admin_url( is_multisite() ? 'settings.php' : 'options-general.php' ),
185 'clear_transient_url' => network_admin_url( 'admin.php?page=wdf-setting&view=configs&transient=clear' ),
186 ),
187 'configs' => $configs,
188 ),
189 $this->dump_routes_and_nonces()
190 );
191 }
192
193 public function to_array() {}
194
195 public function import_data( $data ) {
196 $model = $this->get_model();
197
198 $model->import( $data );
199 if ( $model->validate() ) {
200 $model->save();
201 }
202 }
203
204 private function validate_importer( $importer ) {
205 if ( $this->service->verify_config_data( $importer ) ){
206 // Validate content. This is the current data, we use this for verify the schema.
207 $sample = $this->service->gather_data();
208 foreach ( $importer['configs'] as $slug => $module ) {
209 // This is not in the sample, file is invalid.
210 if ( ! isset( $sample[ $slug ] ) ) {
211 return false;
212 }
213
214 $keys = array_keys( $sample[ $slug ] );
215 $import_keys = array_keys( $module );
216 $diff = array_diff( $import_keys, $keys );
217 if ( count( $diff ) ) {
218 return false;
219 }
220
221 return true;
222 }
223 }
224
225 return false;
226 }
227
228 /**
229 * Import config.
230 * @param Request $request
231 *
232 * @return Response
233 * @defender_route
234 */
235 public function import_config( Request $request ) {
236 $data = $request->get_data();
237
238 $file = $_FILES['file'];
239 $tmp = $file['tmp_name'];
240 $content = file_get_contents( $tmp );
241 $importer = json_decode( $content, true );
242 if ( ! is_array( $importer ) ) {
243 return new Response(
244 false,
245 array(
246 'message' => __( 'The file is corrupted.', 'wpdef' ),
247 )
248 );
249 }
250
251 // If it's old config structure then we upgrade configs to new format.
252 if ( ! empty( $importer['configs'] ) && ! $this->service->check_for_new_structure( $importer['configs'] ) ) {
253 $adapter = wd_di()->get( \WP_Defender\Component\Config\Config_Adapter::class );
254 $importer['configs'] = $adapter->upgrade( $importer['configs'] );
255 }
256
257 if ( ! $this->validate_importer( $importer ) ) {
258 return new Response(
259 false,
260 array(
261 'message' => __( 'An error occurred while importing the file. Please check your file or upload another file.', 'wpdef' ),
262 )
263 );
264 }
265
266 // Sanitize the files a bit.
267 $name = strip_tags( $importer['name'] );
268 $configs = array(
269 'name' => $name,
270 'immortal' => false,
271 );
272
273 $configs['configs'] = $importer['configs'];
274 $configs['description'] = isset( $importer['description'] ) && ! empty( $importer['description'] )
275 ? sanitize_textarea_field( $importer['description'] )
276 : '';
277 $configs['strings'] = $this->service->import_module_strings( $importer );
278 $key = 'wp_defender_config_import_' . time();
279 update_site_option( $key, $configs );
280 $this->service->index_key( $key );
281
282 return new Response(
283 true,
284 array(
285 'message' => sprintf(
286 /* translators: %s: Config name. */
287 __(
288 '<strong>%s</strong> config has been uploaded successfully – you can now apply it to this site.',
289 'wpdef'
290 ),
291 $name
292 ),
293 'configs' => Config_Hub_Helper::get_fresh_frontend_configs( $this->service ),
294 )
295 );
296 }
297
298 /**
299 * Create config.
300 * @param Request $request
301 *
302 * @return Response
303 * @defender_route
304 */
305 public function new_config( Request $request ) {
306 $data = $request->get_data();
307 $name = trim( $data['name'] );
308 if ( empty( $name ) ) {
309 return new Response(
310 false,
311 array(
312 'message' => __( 'Invalid config name', 'wpdef' ),
313 )
314 );
315 }
316 $name = strip_tags( $name );
317 $desc = isset( $data['desc'] ) && ! empty( $data['desc'] ) ? wp_kses_post( $data['desc'] ) : '';
318 $key = 'wp_defender_config_' . time();
319 $settings = $this->service->parse_data_for_import();
320 $data = array_merge(
321 array(
322 'name' => $name,
323 'immortal' => false,
324 'description' => $desc,
325 ),
326 $settings
327 );
328
329 // Add config to HUB.
330 $hub_id = Config_Hub_Helper::add_configs_to_hub( $data );
331
332 if ( $hub_id ) {
333 $data['hub_id'] = $hub_id;
334 }
335
336 unset( $data['labels'] );
337
338 if ( update_site_option( $key, $data ) ) {
339 $this->service->index_key( $key );
340
341 return new Response(
342 true,
343 array(
344 'message' => sprintf(
345 /* translators: %s: Config name. */
346 __( '<strong>%s</strong> config saved successfully.', 'wpdef' ),
347 $name
348 ),
349 'configs' => Config_Hub_Helper::get_fresh_frontend_configs( $this->service ),
350 )
351 );
352 } else {
353 return new Response(
354 false,
355 array(
356 'message' => __( 'An error occurred while saving your config. Please try it again.', 'wpdef' ),
357 )
358 );
359 }
360 }
361
362 /**
363 * Download config
364 *
365 * @defender_route
366 */
367 public function download_config() {
368 $key = isset( $_GET['key'] ) ? sanitize_text_field( $_GET['key'] ) : false;
369 if ( empty( $key ) ) {
370 return new Response(
371 false,
372 array(
373 'message' => __( 'Invalid config', 'wpdef' ),
374 )
375 );
376 }
377
378 $config = get_site_option( $key );
379 if ( false === $config ) {
380 return new Response(
381 false,
382 array(
383 'message' => __( 'Invalid config', 'wpdef' ),
384 )
385 );
386 }
387 $sample = $this->service->gather_data();
388 foreach ( $sample as $slug => $data ) {
389 foreach ( $data as $key => $val ) {
390 if ( ! isset( $config['configs'][ $slug ][ $key ] ) ) {
391 $config['configs'][ $slug ][ $key ] = null;
392 }
393 }
394 }
395 $json = json_encode( $config );
396 $filename = 'wp-defender-config-' . sanitize_file_name( $config['name'] ) . '.json';
397 header( 'Content-disposition: attachment; filename=' . $filename );
398 header( 'Content-type: application/json' );
399 echo $json;
400 exit;
401 }
402
403 /**
404 * Apply config.
405 * @param Request $request
406 *
407 * @return Response
408 * @defender_route
409 */
410 public function apply_config( Request $request ) {
411 $data = $request->get_data();
412 $key = trim( $data['key'] );
413 if ( empty( $key ) ) {
414 return new Response(
415 false,
416 array(
417 'message' => __( 'Invalid config', 'wpdef' ),
418 )
419 );
420 }
421
422 $config = get_site_option( $key );
423 if ( false === $config ) {
424 return new Response(
425 false,
426 array(
427 'message' => __( 'Invalid config', 'wpdef' ),
428 )
429 );
430 }
431 // Return error message or bool value for auth action.
432 $restore_result = $this->service->restore_data( $config['configs'], 'plugin' );
433 if ( is_string( $restore_result ) ) {
434 return $this->apply_config_recommendations_error_message();
435 }
436
437 $this->service->make_config_active( $key );
438 $message = sprintf(
439 /* translators: %s: Config name. */
440 __(
441 '<strong>%s</strong> config has been applied successfully.',
442 'wpdef'
443 ),
444 $config['name']
445 );
446 $return = array();
447 if ( $restore_result ) {
448 $login_url = wp_login_url();
449 $settings_mask_login = new \WP_Defender\Model\Setting\Mask_Login();
450 if ( $settings_mask_login->is_active() ) {
451 $login_url = $settings_mask_login->get_new_login_url();
452 }
453 $message .= '<br/>' . sprintf(
454 /* translators: %s: Login link. */
455 __(
456 'Because of some security recommendations get applied, You will now need to <a href="%s"><strong>re-login</strong></a>.<br/>This will auto reload now',
457 'wpdef'
458 ),
459 $login_url
460 );
461 $return['reload'] = 3;
462 $redirect = urlencode( network_admin_url( 'admin.php?page=wdf-setting&view=configs' ) );
463 if ( isset( $data['screen'] ) && 'dashboard' === $data['screen'] ) {
464 $redirect = urlencode( network_admin_url( 'admin.php?page=wp-defender' ) );
465 }
466 $return['redirect'] = add_query_arg(
467 'redirect_to',
468 $redirect,
469 $login_url
470 );
471 $return['interval'] = 2;
472 }
473
474 $return['message'] = $message;
475 $return['configs'] = Config_Hub_Helper::get_fresh_frontend_configs( $this->service );
476
477 return new Response( true, $return );
478 }
479
480 /**
481 * Update config.
482 * @param Request $request
483 *
484 * @return Response
485 * @defender_route
486 */
487 public function update_config( Request $request ) {
488 $data = $request->get_data();
489 $key = trim( $data['key'] );
490 $name = trim( $data['name'] );
491 $description = trim( $data['description'] );
492 if ( empty( $name ) || empty( $key ) ) {
493 return new Response(
494 false,
495 array(
496 'message' => __( 'Invalid config', 'wpdef' ),
497 )
498 );
499 }
500
501 $config = get_site_option( $key );
502 if ( false === $config ) {
503 return new Response(
504 false,
505 array(
506 'message' => __( 'Invalid config', 'wpdef' ),
507 )
508 );
509 }
510
511 $old_config = $config;
512 $config['name'] = sanitize_text_field( $name );
513 $config['description'] = sanitize_textarea_field( $description );
514
515 // Check data has been changed or not.
516 if (
517 $old_config['name'] === $config['name'] &&
518 $old_config['description'] === $config['description']
519 ) {
520 // Data is not changed, so not need to run update query.
521 $option_updated = true;
522 } else {
523 $option_updated = update_site_option( $key, $config );
524 Config_Hub_Helper::update_on_hub( $config );
525 }
526
527 if ( $option_updated ) {
528 return new Response(
529 true,
530 array(
531 'message' => sprintf(
532 /* translators: %s: Config name. */
533 __( '<strong>%s</strong> config saved successfully.', 'wpdef' ),
534 $name
535 ),
536 'configs' => Config_Hub_Helper::get_fresh_frontend_configs( $this->service ),
537 )
538 );
539 } else {
540 return new Response(
541 false,
542 array(
543 'message' => __( 'An error occurred while saving your config. Please try it again.', 'wpdef' ),
544 )
545 );
546 }
547 }
548
549 /**
550 * Delete config.
551 * @param Request $request
552 *
553 * @return Response
554 * @defender_route
555 */
556 public function delete_config( Request $request ) {
557 $data = $request->get_data();
558 $key = trim( $data['key'] );
559 if ( empty( $key ) ) {
560 return new Response(
561 false,
562 array(
563 'message' => __( 'Invalid config', 'wpdef' ),
564 )
565 );
566 }
567
568 // Remove from HUB.
569 $config = get_site_option( $key );
570 if ( isset( $config['hub_id'] ) ) {
571 Config_Hub_Helper::delete_configs_from_hub( $config['hub_id'] );
572 }
573
574 if ( 0 === strpos( $key, 'wp_defender_config' ) ) {
575 delete_site_option( $key );
576
577 return new Response(
578 true,
579 array(
580 'message' => __( 'Config removed successfully.', 'wpdef' ),
581 'configs' => Config_Hub_Helper::get_fresh_frontend_configs( $this->service ),
582 )
583 );
584 }
585
586 return new Response(
587 false,
588 array(
589 'message' => __( 'Invalid config', 'wpdef' ),
590 )
591 );
592 }
593
594 /**
595 * @return array
596 */
597 public function export_strings() {
598 return array();
599 }
600
601 /**
602 * Update config status and return them.
603 *
604 * @return array
605 */
606 private function get_configs_and_update_status() {
607 $configs = Config_Hub_Helper::get_configs( $this->service );
608 $is_remove = Config_Hub_Helper::check_remove_active_flag();
609
610 // Loop to update strings of configs.
611 foreach ( $configs as $key => &$config ) {
612 if ( ! is_array( $config ) ) {
613 continue;
614 }
615
616 $config['strings'] = $this->service->import_module_strings( $config );
617
618 if ( $is_remove ) {
619 $config['is_active'] = false;
620 }
621
622 // Update config data.
623 update_site_option( $key, $config );
624 }
625
626 return $configs;
627 }
628
629 /**
630 * Response error message along with configs.
631 *
632 * @return Response
633 *
634 * @throws \Exception
635 */
636 private function apply_config_recommendations_error_message() {
637 $message = sprintf(
638 __( 'There was an issue with applying some of the tweaks from the <strong>Recommendations</strong> tab because we cannot make changes to your <strong>wp-config.php</strong> file. Please see our %s to apply the changes manually.', 'wpdef' ),
639 '<a href="https://wpmudev.com/docs/wpmu-dev-plugins/defender/#manually-applying-recommendations">documentation</a>'
640 );
641
642 return new Response(
643 false,
644 array(
645 'message' => $message,
646 'configs' => Config_Hub_Helper::get_fresh_frontend_configs( $this->service ),
647 )
648 );
649 }
650
651 /**
652 * Check if the logger cron is scheduled to run.
653 */
654 public function check_cron_schedule() {
655 if ( ! wp_next_scheduled( 'wp_defender_clear_logs' ) ) {
656 wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'wp_defender_clear_logs' );
657 }
658 }
659
660 /**
661 * Clear out lines that are older than 30 days.
662 */
663 public function clear_logs() {
664 // @since 2.7.0
665 $time_limit = apply_filters( 'wpdef_clear_logs_time_limit', MONTH_IN_SECONDS );
666
667 if ( is_multisite() ) {
668 global $wpdb;
669 $offset = 0;
670 $limit = 100;
671 while ( $blogs = $wpdb->get_results( "SELECT blog_id FROM {$wpdb->blogs} LIMIT {$offset}, {$limit}", ARRAY_A ) ) {
672 if ( ! empty( $blogs ) && is_array( $blogs ) ) {
673 foreach ( $blogs as $blog ) {
674 switch_to_blog( $blog['blog_id'] );
675
676 $this->clear_logs_from_files( $time_limit );
677
678 restore_current_blog();
679 }
680 }
681 $offset += $limit;
682 }
683 } else {
684 $this->clear_logs_from_files( $time_limit );
685 }
686 }
687
688 /**
689 * Clear logs from file that are 30 days old.
690 *
691 * @param int $time_limit
692 *
693 * @since 2.7.0
694 */
695 public function clear_logs_from_files( $time_limit = MONTH_IN_SECONDS ) {
696 $now = date( 'c' );
697 // Todo: why only defender.log?
698 $files = array( 'defender.log' );
699
700 foreach ( $files as $file_name ) {
701 $file_path = $this->get_log_path( $file_name );
702
703 if ( ! file_exists( $file_path ) ) {
704 return;
705 }
706
707 $content = file( $file_path );
708 $size_of_content = is_array($content) || $content instanceof \Countable ? count( $content ) : 0;
709
710 foreach ( $content as $index => $line ) {
711 // If the line does not start with '[' (it's probably not a new entry).
712 $first_char = substr( $line, 0, 1 );
713
714 if ( '[' !== $first_char ) {
715 // Delete.
716 unset( $content[ $index ] );
717 }
718
719 /**
720 * Get the date from entry. Items can be an array it two cases - if there's a valid date, or if the line
721 * contained something like [header] in the start. Cannot make assumptions just on the fact it's an array.
722 */
723 preg_match( '/\[(.*)\]/', $line, $items );
724
725 // If, for some reason, can't get the date, or it's not the size of an ISO 8601 date.
726 if ( ! isset( $items[1] ) || 25 !== strlen( $items[1] ) ) {
727 // Delete.
728 unset( $content[ $index ] );
729 } else {
730 // It looks like it's a valid date string, compare with today.
731 $time_diff = strtotime( $now ) - strtotime( $items[1] );
732
733 // We don't need to continue on, because if this entry is not older than 30 days, the next one will not be as well.
734 if ( $time_diff < $time_limit ) {
735 break;
736 }
737
738 unset( $content[ $index ] );
739 }
740 }
741
742 // Nothing changed - do nothing.
743 if ( (is_array($content) || $content instanceof \Countable ? count( $content ) : 0) === $size_of_content ) {
744 return;
745 }
746
747 // Glue back together and write back to file.
748 $content = implode( '', $content );
749
750 file_put_contents( $file_path, $content );
751 }
752 }
753 }
754