PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.7.2
Defender Security – Malware Scanner, Login Security & Firewall v5.7.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 / controller / class-main-setting.php

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

950 lines 25.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles the main settings.
4 *
5 * @package WP_Defender\Controller
6 */
7
8 namespace WP_Defender\Controller;
9
10 use Countable;
11 use WP_Defender\Event;
12 use Calotes\Component\Request;
13 use Calotes\Component\Response;
14 use WP_Defender\Behavior\WPMUDEV;
15 use WP_Defender\Component\Backup_Settings;
16 use WP_Defender\Component\Config\Config_Adapter;
17 use WP_Defender\Component\Config\Config_Hub_Helper;
18 use WP_Defender\Model\Setting\Main_Setting as Model_Main_Setting;
19 use WP_Filesystem_Base;
20
21 /**
22 * Methods for handling main settings.
23 */
24 class Main_Setting extends Event {
25
26 /**
27 * The slug identifier for this controller.
28 *
29 * @var string
30 */
31 public $slug = 'wdf-setting';
32
33 /**
34 * The model for handling the data.
35 *
36 * @var Model_Main_Setting
37 */
38 public $model;
39
40 /**
41 * Service for handling logic.
42 *
43 * @var Backup_Settings
44 */
45 protected $service;
46
47 /**
48 * Service for handling WPMUDEV.
49 *
50 * @var WPMUDEV
51 */
52 protected $wpmudev;
53
54 /**
55 * The intention/nonce action of the current request.
56 *
57 * @since 4.0.0
58 * @var string
59 */
60 protected $intention = '';
61
62 /**
63 * Initializes the model and service, registers routes, and sets up scheduled events if the model is active.
64 */
65 public function __construct() {
66 $this->register_page(
67 esc_html__( 'Settings', 'defender-security' ),
68 $this->slug,
69 array( $this, 'main_view' ),
70 $this->parent_slug
71 );
72
73 // Internal cache.
74 $this->model = new Model_Main_Setting();
75 $this->service = wd_di()->get( Backup_Settings::class );
76 $this->wpmudev = wd_di()->get( WPMUDEV::class );
77 add_action( 'defender_enqueue_assets', array( $this, 'enqueue_assets' ) );
78 $this->register_routes();
79
80 // Add cron schedule to clean out outdated logs.
81 add_action( 'wp_defender_clear_logs', array( $this, 'clear_logs' ) );
82 add_action( 'admin_init', array( $this, 'check_cron_schedule' ) );
83 add_action( 'wd_settings_update', array( $this, 'intercept_settings_update' ), 10, 2 );
84 }
85
86 /**
87 * Check actual config data.
88 *
89 * @return Response
90 * @defender_route
91 */
92 public function check_configs(): Response {
93 Config_Hub_Helper::clear_config_transient();
94
95 return new Response( true, array() );
96 }
97
98 /**
99 * Safe way to get cached model.
100 *
101 * @return Model_Main_Setting
102 */
103 private function get_model() {
104 if ( is_object( $this->model ) ) {
105 return $this->model;
106 }
107
108 return wd_di()->get( Model_Main_Setting::class );
109 }
110
111 /**
112 * Enqueues scripts and styles for this page.
113 * Only enqueues assets if the page is active.
114 */
115 public function enqueue_assets() {
116 if ( ! $this->is_page_active() ) {
117 return;
118 }
119 wp_localize_script( 'def-settings', 'settings', $this->data_frontend() );
120 wp_enqueue_script( 'def-settings' );
121 $this->enqueue_main_assets();
122 }
123
124 /**
125 * Render the root element for frontend.
126 *
127 * @return void
128 */
129 public function main_view(): void {
130 $this->render( 'main' );
131 }
132
133 /**
134 * Save settings.
135 *
136 * @param Request $request The request object containing new settings data.
137 *
138 * @return Response
139 * @defender_route
140 */
141 public function save_settings( Request $request ) {
142 $model = $this->get_model();
143 $data = $request->get_data();
144
145 $model->import( $data );
146 if ( $model->validate() ) {
147 $this->set_intention( 'Settings' );
148 $model->save();
149 Config_Hub_Helper::set_clear_active_flag();
150
151 return new Response(
152 true,
153 array(
154 'message' => esc_html__( 'Your settings have been updated.', 'defender-security' ),
155 'auto_close' => true,
156 )
157 );
158 }
159
160 return new Response(
161 false,
162 array(
163 'message' => $model->get_formatted_errors(),
164 )
165 );
166 }
167
168 /**
169 * Reset settings.
170 *
171 * @return Response
172 * @defender_route
173 */
174 public function reset_settings(): Response {
175 wd_di()->get( Advanced_Tools::class )->remove_settings();
176 wd_di()->get( Audit_Logging::class )->remove_settings();
177 wd_di()->get( Dashboard::class )->remove_settings();
178 wd_di()->get( Security_Tweaks::class )->remove_settings();
179 wd_di()->get( Scan::class )->remove_settings();
180 // Parent and submodules.
181 wd_di()->get( Firewall::class )->remove_settings();
182
183 wd_di()->get( Mask_Login::class )->remove_settings();
184 wd_di()->get( Notification::class )->remove_settings();
185 wd_di()->get( Two_Factor::class )->remove_settings();
186 wd_di()->get( Blocklist_Monitor::class )->remove_settings();
187 $this->set_intention( 'Data Reset' );
188 // Track first until settings are removed.
189 $this->track_opt( false );
190 $this->remove_settings();
191 // Indicate that it is not a new installation.
192 defender_no_fresh_install();
193
194 return new Response(
195 true,
196 array(
197 'message' => esc_html__( 'Your settings have been reset.', 'defender-security' ),
198 'redirect' => network_admin_url( 'admin.php?page=wp-defender' ),
199 'interval' => 1,
200 )
201 );
202 }
203
204 /**
205 * Tracks the settings toggle.
206 *
207 * @param bool $active The status of the toggle.
208 *
209 * @return void
210 */
211 public function track_opt( $active ) {
212 $model = $this->get_model();
213 // Track only if the Data tracking option was enabled before changes.
214 if ( $model->usage_tracking ) {
215 $from = $this->get_triggered_location();
216 $this->track_opt_toggle( $active, $from );
217 }
218 }
219
220 /**
221 * Removes settings for all submodules.
222 */
223 public function remove_settings(): void {
224 wd_di()->get( Model_Main_Setting::class )->delete();
225 }
226
227 /**
228 * Delete all the data & the cache.
229 */
230 public function remove_data() {
231 }
232
233 /**
234 * Provides data for the frontend.
235 *
236 * @return array An array of data for the frontend.
237 */
238 public function data_frontend(): array {
239 $model = $this->get_model();
240
241 $this->service->maybe_create_default_config();
242 $configs = $this->get_configs_and_update_status();
243
244 foreach ( $configs as &$config ) {
245 // Unset the data as we don't need it.
246 if ( isset( $config['configs'] ) ) {
247 unset( $config['configs'] );
248 }
249 }
250
251 $link = $this->wpmudev->is_member()
252 ? 'https://wpmudev.com/translate/projects/wpdef/'
253 : 'https://translate.wordpress.org/projects/wp-plugins/defender-security/';
254 $can_whitelabel = wd_di()->get( \WP_Defender\Integrations\Dashboard_Whitelabel::class )->can_whitelabel();
255
256 return array_merge(
257 array(
258 'general' => array(
259 'translate' => $model->translate,
260 'show_usage_tracking' => $this->wpmudev->is_wpmu_dev_admin() || ! $can_whitelabel,
261 'usage_tracking' => $model->usage_tracking,
262 'translation_link' => $link,
263 ),
264 'data_settings' => array(
265 'uninstall_settings' => $model->uninstall_settings,
266 'uninstall_data' => $model->uninstall_data,
267 'uninstall_quarantine' => $model->uninstall_quarantine,
268 ),
269 'accessibility' => array(
270 'high_contrast_mode' => $model->high_contrast_mode,
271 ),
272 'misc' => array(
273 'setting_url' => network_admin_url( is_multisite() ? 'settings.php' : 'options-general.php' ),
274 'privacy_link' => Model_Main_Setting::PRIVACY_LINK,
275 ),
276 'configs' => $configs,
277 'hub_connector' => wd_di()->get( Hub_Connector::class )->data_frontend(),
278 'antibot' => wd_di()->get( Antibot_Global_Firewall::class )->data_frontend(),
279 ),
280 $this->dump_routes_and_nonces()
281 );
282 }
283
284 /**
285 * Converts the current object state to an array.
286 *
287 * @return array The array representation of the object.
288 */
289 public function to_array(): array {
290 return array();
291 }
292
293 /**
294 * Imports data into the model.
295 *
296 * @param array $data Data to be imported into the model.
297 */
298 public function import_data( array $data ) {
299 $model = $this->get_model();
300
301 $model->import( $data );
302 if ( $model->validate() ) {
303 $model->save();
304 }
305 }
306
307 /**
308 * Validates the importer data.
309 * This function checks if the importer data is valid by verifying its configuration data and comparing it with the
310 * sample data.
311 *
312 * @param array $importer The importer data to be validated.
313 *
314 * @return bool Returns true if the importer data is valid, false otherwise.
315 */
316 private function validate_importer( $importer ): bool {
317 if ( $this->service->verify_config_data( $importer ) ) {
318 // Validate content. This is the current data, we use this for verify the schema.
319 $sample = $this->service->gather_data();
320 foreach ( $importer['configs'] as $slug => $module ) {
321 // This is not in the sample, file is invalid.
322 if ( ! isset( $sample[ $slug ] ) ) {
323 return false;
324 }
325
326 $keys = array_keys( $sample[ $slug ] );
327 $import_keys = array_keys( $module );
328 $diff = array_diff( $import_keys, $keys );
329 if ( count( $diff ) ) {
330 return false;
331 }
332
333 return true;
334 }
335 }
336
337 return false;
338 }
339
340 /**
341 * Import config.
342 *
343 * @return Response
344 * @defender_route
345 */
346 public function import_config(): Response {
347 global $wp_filesystem;
348 // Initialize the WP filesystem, no more using 'file-put-contents' function.
349 if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) {
350 require_once ABSPATH . '/wp-admin/includes/file.php';
351 WP_Filesystem();
352 }
353 $file = defender_get_data_from_request( 'file', 'f' );
354 $tmp = $file['tmp_name'];
355 $content = $wp_filesystem->get_contents( $tmp );
356 $importer = json_decode( $content, true );
357 if ( ! is_array( $importer ) ) {
358 return new Response(
359 false,
360 array(
361 'message' => esc_html__( 'The file is corrupted.', 'defender-security' ),
362 )
363 );
364 }
365
366 // If it's old config structure then we upgrade configs to new format.
367 if ( ! empty( $importer['configs'] ) && ! $this->service->check_for_new_structure( $importer['configs'] ) ) {
368 $adapter = wd_di()->get( Config_Adapter::class );
369 $importer['configs'] = $adapter->upgrade( $importer['configs'] );
370 }
371
372 if ( ! $this->validate_importer( $importer ) ) {
373 return new Response(
374 false,
375 array(
376 'message' => esc_html__(
377 'An error occurred while importing the file. Please check your file or upload another file.',
378 'defender-security'
379 ),
380 )
381 );
382 }
383
384 // Do not use wp_strip_all_tags() to prevent XSS attack.
385 $name = sanitize_text_field( $importer['name'] );
386 $configs = array(
387 'name' => $name,
388 'immortal' => false,
389 'is_removable' => true,
390 );
391
392 $configs['configs'] = $importer['configs'];
393 $configs['description'] = isset( $importer['description'] ) && ! empty( $importer['description'] )
394 ? sanitize_textarea_field( $importer['description'] )
395 : '';
396 $configs['strings'] = $this->service->import_module_strings( $importer );
397 $key = 'wp_defender_config_import_' . time();
398 update_site_option( $key, $configs );
399 $this->service->index_key( $key );
400
401 return new Response(
402 true,
403 array(
404 'message' => sprintf(
405 /* translators: %s: Config name. */
406 esc_html__(
407 '%s config has been uploaded successfully – you can now apply it to this site.',
408 'defender-security'
409 ),
410 '<strong>' . $name . '</strong>'
411 ),
412 'configs' => Config_Hub_Helper::get_fresh_frontend_configs( $this->service ),
413 )
414 );
415 }
416
417 /**
418 * Create config.
419 *
420 * @param Request $request The request object containing new config data.
421 *
422 * @return Response
423 * @defender_route
424 */
425 public function new_config( Request $request ): Response {
426 $data = $request->get_data();
427 $name = trim( $data['name'] );
428 if ( empty( $name ) ) {
429 return new Response(
430 false,
431 array(
432 'message' => esc_html__( 'Invalid config name', 'defender-security' ),
433 )
434 );
435 }
436 $name = sanitize_text_field( $name );
437 $desc = isset( $data['desc'] ) && ! empty( $data['desc'] ) ? wp_kses_post( $data['desc'] ) : '';
438 $key = 'wp_defender_config_' . time();
439 $settings = $this->service->parse_data_for_import();
440 $data = array_merge(
441 array(
442 'name' => $name,
443 'immortal' => false,
444 'description' => $desc,
445 'is_removable' => true,
446 ),
447 $settings
448 );
449
450 // Add config to HUB.
451 $hub_id = Config_Hub_Helper::add_configs_to_hub( $data );
452
453 if ( $hub_id ) {
454 $data['hub_id'] = $hub_id;
455 }
456
457 unset( $data['labels'] );
458
459 if ( update_site_option( $key, $data ) ) {
460 $this->service->index_key( $key );
461
462 return new Response(
463 true,
464 array(
465 'message' => sprintf(
466 /* translators: %s: Config name. */
467 esc_html__( '%s config saved successfully.', 'defender-security' ),
468 '<strong>' . $name . '</strong>'
469 ),
470 'configs' => Config_Hub_Helper::get_fresh_frontend_configs( $this->service ),
471 )
472 );
473 } else {
474 return new Response(
475 false,
476 array(
477 'message' => esc_html__(
478 'An error occurred while saving your config. Please try it again.',
479 'defender-security'
480 ),
481 )
482 );
483 }
484 }
485
486 /**
487 * Download config
488 *
489 * @return Response|void
490 * @defender_route
491 */
492 public function download_config() {
493 $key = defender_get_data_from_request( 'key', 'g' );
494 if ( empty( $key ) ) {
495 return new Response(
496 false,
497 array(
498 'message' => esc_html__( 'Invalid config', 'defender-security' ),
499 )
500 );
501 }
502
503 $config = get_site_option( $key );
504 if ( false === $config ) {
505 return new Response(
506 false,
507 array(
508 'message' => esc_html__( 'Invalid config', 'defender-security' ),
509 )
510 );
511 }
512 $sample = $this->service->gather_data();
513 foreach ( $sample as $slug => $data ) {
514 foreach ( $data as $key => $val ) {
515 if ( ! isset( $config['configs'][ $slug ][ $key ] ) ) {
516 $config['configs'][ $slug ][ $key ] = null;
517 }
518 }
519 }
520 $filename = 'wp-defender-config-' . sanitize_file_name( $config['name'] ) . '.json';
521 header( 'Content-disposition: attachment; filename=' . $filename );
522 echo wp_json_encode( $config, JSON_PRETTY_PRINT );
523 exit();
524 }
525
526 /**
527 * Apply config.
528 *
529 * @param Request $request The request object containing new config data.
530 *
531 * @return Response
532 * @defender_route
533 */
534 public function apply_config( Request $request ) {
535 $data = $request->get_data();
536 $key = trim( $data['key'] );
537 if ( empty( $key ) ) {
538 return new Response(
539 false,
540 array(
541 'message' => esc_html__( 'Invalid config', 'defender-security' ),
542 )
543 );
544 }
545
546 $config = get_site_option( $key );
547 if ( false === $config ) {
548 return new Response(
549 false,
550 array(
551 'message' => esc_html__( 'Invalid config', 'defender-security' ),
552 )
553 );
554 }
555 // Return error message or bool value for auth action.
556 $restore_result = $this->service->restore_data( $config['configs'], 'plugin' );
557 if ( is_string( $restore_result ) ) {
558 return $this->apply_config_recommendations_error_message();
559 }
560
561 $this->service->make_config_active( $key );
562 // Track.
563 $this->track_feature(
564 'def_config_applied',
565 array(
566 // The check is based on the fact that the Default config cannot be deleted.
567 'Config Type' => isset( $config['is_removable'] ) && false === $config['is_removable'] ? 'Default' : 'Custom',
568 )
569 );
570
571 $message = sprintf(
572 /* translators: %s: Config name. */
573 esc_html__(
574 '%s config has been applied successfully.',
575 'defender-security'
576 ),
577 '<strong>' . $config['name'] . '</strong>'
578 );
579 $return = array();
580 if ( $restore_result ) {
581 $login_url = wp_login_url();
582 $settings_mask_login = new \WP_Defender\Model\Setting\Mask_Login();
583 if ( $settings_mask_login->is_active() ) {
584 $login_url = $settings_mask_login->get_new_login_url();
585 }
586 $message .= '<br/>' . sprintf(
587 /* translators: %s: Login link. */
588 esc_html__(
589 'Due to currently applied security recommendations, you will now need to %s.',
590 'defender-security'
591 ),
592 '<a href="' . $login_url . '"><strong>' . esc_html__( 're-login', 'defender-security' ) . '</strong></a>'
593 );
594 $message .= '<br/>';
595 $message .= esc_html__( 'This will auto reload now.', 'defender-security' );
596
597 $return['reload'] = 3;
598 $redirect = rawurlencode( network_admin_url( 'admin.php?page=wdf-setting&view=configs' ) );
599 if ( isset( $data['screen'] ) && 'dashboard' === $data['screen'] ) {
600 $redirect = rawurlencode( network_admin_url( 'admin.php?page=wp-defender' ) );
601 }
602 $return['redirect'] = add_query_arg(
603 'redirect_to',
604 $redirect,
605 $login_url
606 );
607 $return['interval'] = 2;
608 }
609
610 $return['message'] = $message;
611 $return['auto_close'] = true;
612 $return['configs'] = Config_Hub_Helper::get_fresh_frontend_configs( $this->service );
613
614 return new Response( true, $return );
615 }
616
617 /**
618 * Update config.
619 *
620 * @param Request $request The request object containing new config data.
621 *
622 * @return Response
623 * @defender_route
624 */
625 public function update_config( Request $request ) {
626 $data = $request->get_data();
627 $key = trim( $data['key'] );
628 $name = trim( $data['name'] );
629 $description = trim( $data['desc'] );
630 if ( empty( $name ) || empty( $key ) ) {
631 return new Response(
632 false,
633 array(
634 'message' => esc_html__( 'Invalid config', 'defender-security' ),
635 )
636 );
637 }
638
639 $config = get_site_option( $key );
640 if ( false === $config ) {
641 return new Response(
642 false,
643 array(
644 'message' => esc_html__( 'Invalid config', 'defender-security' ),
645 )
646 );
647 }
648
649 $old_config = $config;
650 $config['name'] = sanitize_text_field( $name );
651 $config['description'] = sanitize_textarea_field( $description );
652
653 // Check data has been changed or not.
654 if (
655 $old_config['name'] === $config['name'] &&
656 $old_config['description'] === $config['description']
657 ) {
658 // Data is not changed, so not need to run update query.
659 $option_updated = true;
660 } else {
661 $option_updated = update_site_option( $key, $config );
662 Config_Hub_Helper::update_on_hub( $config );
663 }
664
665 if ( $option_updated ) {
666 return new Response(
667 true,
668 array(
669 'message' => sprintf(
670 /* translators: %s: Config name. */
671 esc_html__( '%s config saved successfully.', 'defender-security' ),
672 '<strong>' . $name . '</strong>'
673 ),
674 'auto_close' => true,
675 'configs' => Config_Hub_Helper::get_fresh_frontend_configs( $this->service ),
676 )
677 );
678 } else {
679 return new Response(
680 false,
681 array(
682 'message' => esc_html__(
683 'An error occurred while saving your config. Please try it again.',
684 'defender-security'
685 ),
686 )
687 );
688 }
689 }
690
691 /**
692 * Delete config.
693 *
694 * @param Request $request The request object containing config key.
695 *
696 * @return Response
697 * @defender_route
698 */
699 public function delete_config( Request $request ) {
700 $data = $request->get_data();
701 $key = trim( $data['key'] );
702 if ( empty( $key ) ) {
703 return new Response(
704 false,
705 array(
706 'message' => esc_html__( 'Invalid config', 'defender-security' ),
707 )
708 );
709 }
710
711 $config = get_site_option( $key );
712 if ( isset( $config['is_removable'] ) && ! $config['is_removable'] ) {
713 return new Response(
714 false,
715 array(
716 'message' => esc_html__( 'Config can\'t be removed', 'defender-security' ),
717 )
718 );
719 }
720
721 // Remove from HUB.
722 if ( isset( $config['hub_id'] ) ) {
723 Config_Hub_Helper::delete_configs_from_hub( (int) $config['hub_id'] );
724 }
725
726 if ( 0 === strpos( $key, 'wp_defender_config' ) ) {
727 delete_site_option( $key );
728 $this->service->clear_keys();
729
730 return new Response(
731 true,
732 array(
733 'message' => esc_html__( 'Config removed successfully.', 'defender-security' ),
734 'auto_close' => true,
735 'configs' => Config_Hub_Helper::get_fresh_frontend_configs( $this->service ),
736 )
737 );
738 }
739
740 return new Response(
741 false,
742 array(
743 'message' => esc_html__( 'Invalid config', 'defender-security' ),
744 )
745 );
746 }
747
748 /**
749 * Exports strings.
750 *
751 * @return array An array of strings.
752 */
753 public function export_strings(): array {
754 return array();
755 }
756
757 /**
758 * Update config status and return them.
759 *
760 * @return array
761 */
762 private function get_configs_and_update_status() {
763 $configs = Config_Hub_Helper::get_configs( $this->service );
764 $is_remove = Config_Hub_Helper::check_remove_active_flag();
765
766 // Loop to update strings of configs.
767 foreach ( $configs as $key => &$config ) {
768 if ( ! is_array( $config ) ) {
769 continue;
770 }
771
772 $config['strings'] = $this->service->import_module_strings( $config );
773
774 if ( $is_remove ) {
775 $config['is_active'] = false;
776 }
777
778 // Update config data.
779 update_site_option( $key, $config );
780 }
781
782 return $configs;
783 }
784
785 /**
786 * Generates an error message for when there is an issue applying some tweaks from the Recommendations tab.
787 *
788 * @return Response The response object containing the error message and fresh frontend configurations.
789 */
790 private function apply_config_recommendations_error_message(): Response {
791 $message = sprintf(
792 /* translators: 1: Recommendations tab, 2: wp-config.php file, 3: Documentation. */
793 esc_html__(
794 'There was an issue with applying some of the tweaks from the %1$s tab because we cannot make changes to your %2$s file. Please see our %3$s to apply the changes manually.',
795 'defender-security'
796 ),
797 '<strong>' . esc_html__( 'Recommendations', 'defender-security' ) . '</strong>',
798 '<strong>' . esc_html__( 'wp-config.php', 'defender-security' ) . '</strong>',
799 '<a href="' . WP_DEFENDER_DOCS_LINK . '#manually-applying-recommendations" target="_blank">' . esc_html__( 'documentation', 'defender-security' ) . '</a>'
800 );
801
802 return new Response(
803 false,
804 array(
805 'message' => $message,
806 'configs' => Config_Hub_Helper::get_fresh_frontend_configs( $this->service ),
807 )
808 );
809 }
810
811 /**
812 * Check if the logger cron is scheduled to run.
813 *
814 * @return void
815 */
816 public function check_cron_schedule(): void {
817 if ( ! wp_next_scheduled( 'wp_defender_clear_logs' ) ) {
818 wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'wp_defender_clear_logs' );
819 }
820 }
821
822 /**
823 * Clear out lines that are older than 30 days.
824 *
825 * @return void
826 */
827 public function clear_logs(): void {
828 // since 2.7.0.
829 $time_limit = apply_filters( 'wpdef_clear_logs_time_limit', MONTH_IN_SECONDS );
830
831 if ( is_multisite() ) {
832 global $wpdb;
833 $offset = 0;
834 $limit = 100;
835 $blogs = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
836 $wpdb->prepare( "SELECT blog_id FROM {$wpdb->blogs} LIMIT %d, %d", $offset, $limit ),
837 ARRAY_A
838 );
839 while ( ! empty( $blogs ) && is_array( $blogs ) ) {
840 foreach ( $blogs as $blog ) {
841 switch_to_blog( $blog['blog_id'] );
842
843 $this->clear_logs_from_files( $time_limit );
844
845 restore_current_blog();
846 }
847 $offset += $limit;
848 $blogs = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
849 $wpdb->prepare( "SELECT blog_id FROM {$wpdb->blogs} LIMIT %d, %d", $offset, $limit ),
850 ARRAY_A
851 );
852 }
853 } else {
854 $this->clear_logs_from_files( $time_limit );
855 }
856 }
857
858 /**
859 * Clear log files older than the specified time.
860 *
861 * @param int $time_limit The time limit in seconds.
862 *
863 * @return void
864 * @since 2.7.0
865 */
866 public function clear_logs_from_files( int $time_limit = MONTH_IN_SECONDS ) {
867 global $wp_filesystem;
868 // Initialize the WP filesystem, no more using 'file-put-contents' function.
869 if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) {
870 require_once ABSPATH . '/wp-admin/includes/file.php';
871 WP_Filesystem();
872 }
873 $now = wp_date( 'c' );
874 $files = array( wd_internal_log() );
875
876 foreach ( $files as $file_name ) {
877 $file_path = $this->get_log_path( $file_name );
878
879 if ( ! file_exists( $file_path ) ) {
880 return;
881 }
882
883 $content = file( $file_path );
884 $size_of_content = is_array( $content ) || $content instanceof Countable ? count( $content ) : 0;
885
886 foreach ( $content as $index => $line ) {
887 // If the line does not start with '[' (it's probably not a new entry).
888 $first_char = substr( $line, 0, 1 );
889
890 if ( '[' !== $first_char ) {
891 // Delete.
892 unset( $content[ $index ] );
893 }
894
895 /**
896 * Get the date from entry. Items can be an array it two cases - if there's a valid date, or if the line
897 * contained something like [header] in the start. Cannot make assumptions just on the fact it's an array.
898 */
899 preg_match( '/\[(.*)\]/', $line, $items );
900
901 // If, for some reason, can't get the date, or it's not the size of an ISO 8601 date.
902 if ( ! isset( $items[1] ) || 25 !== strlen( $items[1] ) ) {
903 // Delete.
904 unset( $content[ $index ] );
905 } else {
906 // It looks like it's a valid date string, compare with today.
907 $time_diff = strtotime( $now ) - strtotime( $items[1] );
908
909 // We don't need to continue on, because if this entry is not older than specific time, the next one will not be as well.
910 if ( $time_diff < $time_limit ) {
911 break;
912 }
913
914 unset( $content[ $index ] );
915 }
916 }
917
918 // Nothing changed - do nothing.
919 if ( ( is_array( $content ) || $content instanceof Countable ? count( $content ) : 0 ) === $size_of_content ) {
920 return;
921 }
922
923 // Glue back together and write back to file.
924 $content = implode( '', $content );
925
926 $wp_filesystem->put_contents( $file_path, $content );
927 }
928 }
929
930 /**
931 * Track the data if there are settings changes.
932 *
933 * @param array $old_settings Old settings.
934 * @param array $new_settings New settings.
935 *
936 * @return void
937 * @since 4.2.0
938 */
939 public function intercept_settings_update( $old_settings, $new_settings ) {
940 $from = $this->get_triggered_location();
941 if (
942 '' !== $from
943 && isset( $new_settings['usage_tracking'], $old_settings['usage_tracking'] )
944 && $new_settings['usage_tracking'] !== $old_settings['usage_tracking']
945 ) {
946 $this->track_opt_toggle( ! empty( $new_settings['usage_tracking'] ), $from );
947 }
948 }
949 }
950