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

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

948 lines 24.9 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 ),
278 $this->dump_routes_and_nonces()
279 );
280 }
281
282 /**
283 * Converts the current object state to an array.
284 *
285 * @return array The array representation of the object.
286 */
287 public function to_array(): array {
288 return array();
289 }
290
291 /**
292 * Imports data into the model.
293 *
294 * @param array $data Data to be imported into the model.
295 */
296 public function import_data( array $data ) {
297 $model = $this->get_model();
298
299 $model->import( $data );
300 if ( $model->validate() ) {
301 $model->save();
302 }
303 }
304
305 /**
306 * Validates the importer data.
307 * This function checks if the importer data is valid by verifying its configuration data and comparing it with the
308 * sample data.
309 *
310 * @param array $importer The importer data to be validated.
311 *
312 * @return bool Returns true if the importer data is valid, false otherwise.
313 */
314 private function validate_importer( $importer ): bool {
315 if ( $this->service->verify_config_data( $importer ) ) {
316 // Validate content. This is the current data, we use this for verify the schema.
317 $sample = $this->service->gather_data();
318 foreach ( $importer['configs'] as $slug => $module ) {
319 // This is not in the sample, file is invalid.
320 if ( ! isset( $sample[ $slug ] ) ) {
321 return false;
322 }
323
324 $keys = array_keys( $sample[ $slug ] );
325 $import_keys = array_keys( $module );
326 $diff = array_diff( $import_keys, $keys );
327 if ( count( $diff ) ) {
328 return false;
329 }
330
331 return true;
332 }
333 }
334
335 return false;
336 }
337
338 /**
339 * Import config.
340 *
341 * @return Response
342 * @defender_route
343 */
344 public function import_config(): Response {
345 global $wp_filesystem;
346 // Initialize the WP filesystem, no more using 'file-put-contents' function.
347 if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) {
348 require_once ABSPATH . '/wp-admin/includes/file.php';
349 WP_Filesystem();
350 }
351 $file = defender_get_data_from_request( 'file', 'f' );
352 $tmp = $file['tmp_name'];
353 $content = $wp_filesystem->get_contents( $tmp );
354 $importer = json_decode( $content, true );
355 if ( ! is_array( $importer ) ) {
356 return new Response(
357 false,
358 array(
359 'message' => esc_html__( 'The file is corrupted.', 'defender-security' ),
360 )
361 );
362 }
363
364 // If it's old config structure then we upgrade configs to new format.
365 if ( ! empty( $importer['configs'] ) && ! $this->service->check_for_new_structure( $importer['configs'] ) ) {
366 $adapter = wd_di()->get( Config_Adapter::class );
367 $importer['configs'] = $adapter->upgrade( $importer['configs'] );
368 }
369
370 if ( ! $this->validate_importer( $importer ) ) {
371 return new Response(
372 false,
373 array(
374 'message' => esc_html__(
375 'An error occurred while importing the file. Please check your file or upload another file.',
376 'defender-security'
377 ),
378 )
379 );
380 }
381
382 // Do not use wp_strip_all_tags() to prevent XSS attack.
383 $name = sanitize_text_field( $importer['name'] );
384 $configs = array(
385 'name' => $name,
386 'immortal' => false,
387 'is_removable' => true,
388 );
389
390 $configs['configs'] = $importer['configs'];
391 $configs['description'] = isset( $importer['description'] ) && ! empty( $importer['description'] )
392 ? sanitize_textarea_field( $importer['description'] )
393 : '';
394 $configs['strings'] = $this->service->import_module_strings( $importer );
395 $key = 'wp_defender_config_import_' . time();
396 update_site_option( $key, $configs );
397 $this->service->index_key( $key );
398
399 return new Response(
400 true,
401 array(
402 'message' => sprintf(
403 /* translators: %s: Config name. */
404 esc_html__(
405 '%s config has been uploaded successfully – you can now apply it to this site.',
406 'defender-security'
407 ),
408 '<strong>' . $name . '</strong>'
409 ),
410 'configs' => Config_Hub_Helper::get_fresh_frontend_configs( $this->service ),
411 )
412 );
413 }
414
415 /**
416 * Create config.
417 *
418 * @param Request $request The request object containing new config data.
419 *
420 * @return Response
421 * @defender_route
422 */
423 public function new_config( Request $request ): Response {
424 $data = $request->get_data();
425 $name = trim( $data['name'] );
426 if ( empty( $name ) ) {
427 return new Response(
428 false,
429 array(
430 'message' => esc_html__( 'Invalid config name', 'defender-security' ),
431 )
432 );
433 }
434 $name = sanitize_text_field( $name );
435 $desc = isset( $data['desc'] ) && ! empty( $data['desc'] ) ? wp_kses_post( $data['desc'] ) : '';
436 $key = 'wp_defender_config_' . time();
437 $settings = $this->service->parse_data_for_import();
438 $data = array_merge(
439 array(
440 'name' => $name,
441 'immortal' => false,
442 'description' => $desc,
443 'is_removable' => true,
444 ),
445 $settings
446 );
447
448 // Add config to HUB.
449 $hub_id = Config_Hub_Helper::add_configs_to_hub( $data );
450
451 if ( $hub_id ) {
452 $data['hub_id'] = $hub_id;
453 }
454
455 unset( $data['labels'] );
456
457 if ( update_site_option( $key, $data ) ) {
458 $this->service->index_key( $key );
459
460 return new Response(
461 true,
462 array(
463 'message' => sprintf(
464 /* translators: %s: Config name. */
465 esc_html__( '%s config saved successfully.', 'defender-security' ),
466 '<strong>' . $name . '</strong>'
467 ),
468 'configs' => Config_Hub_Helper::get_fresh_frontend_configs( $this->service ),
469 )
470 );
471 } else {
472 return new Response(
473 false,
474 array(
475 'message' => esc_html__(
476 'An error occurred while saving your config. Please try it again.',
477 'defender-security'
478 ),
479 )
480 );
481 }
482 }
483
484 /**
485 * Download config
486 *
487 * @return Response|void
488 * @defender_route
489 */
490 public function download_config() {
491 $key = defender_get_data_from_request( 'key', 'g' );
492 if ( empty( $key ) ) {
493 return new Response(
494 false,
495 array(
496 'message' => esc_html__( 'Invalid config', 'defender-security' ),
497 )
498 );
499 }
500
501 $config = get_site_option( $key );
502 if ( false === $config ) {
503 return new Response(
504 false,
505 array(
506 'message' => esc_html__( 'Invalid config', 'defender-security' ),
507 )
508 );
509 }
510 $sample = $this->service->gather_data();
511 foreach ( $sample as $slug => $data ) {
512 foreach ( $data as $key => $val ) {
513 if ( ! isset( $config['configs'][ $slug ][ $key ] ) ) {
514 $config['configs'][ $slug ][ $key ] = null;
515 }
516 }
517 }
518 $filename = 'wp-defender-config-' . sanitize_file_name( $config['name'] ) . '.json';
519 header( 'Content-disposition: attachment; filename=' . $filename );
520 echo wp_json_encode( $config, JSON_PRETTY_PRINT );
521 exit();
522 }
523
524 /**
525 * Apply config.
526 *
527 * @param Request $request The request object containing new config data.
528 *
529 * @return Response
530 * @defender_route
531 */
532 public function apply_config( Request $request ) {
533 $data = $request->get_data();
534 $key = trim( $data['key'] );
535 if ( empty( $key ) ) {
536 return new Response(
537 false,
538 array(
539 'message' => esc_html__( 'Invalid config', 'defender-security' ),
540 )
541 );
542 }
543
544 $config = get_site_option( $key );
545 if ( false === $config ) {
546 return new Response(
547 false,
548 array(
549 'message' => esc_html__( 'Invalid config', 'defender-security' ),
550 )
551 );
552 }
553 // Return error message or bool value for auth action.
554 $restore_result = $this->service->restore_data( $config['configs'], 'plugin' );
555 if ( is_string( $restore_result ) ) {
556 return $this->apply_config_recommendations_error_message();
557 }
558
559 $this->service->make_config_active( $key );
560 // Track.
561 $this->track_feature(
562 'def_config_applied',
563 array(
564 // The check is based on the fact that the Default config cannot be deleted.
565 'Config Type' => isset( $config['is_removable'] ) && false === $config['is_removable'] ? 'Default' : 'Custom',
566 )
567 );
568
569 $message = sprintf(
570 /* translators: %s: Config name. */
571 esc_html__(
572 '%s config has been applied successfully.',
573 'defender-security'
574 ),
575 '<strong>' . $config['name'] . '</strong>'
576 );
577 $return = array();
578 if ( $restore_result ) {
579 $login_url = wp_login_url();
580 $settings_mask_login = new \WP_Defender\Model\Setting\Mask_Login();
581 if ( $settings_mask_login->is_active() ) {
582 $login_url = $settings_mask_login->get_new_login_url();
583 }
584 $message .= '<br/>' . sprintf(
585 /* translators: %s: Login link. */
586 esc_html__(
587 'Due to currently applied security recommendations, you will now need to %s.',
588 'defender-security'
589 ),
590 '<a href="' . $login_url . '"><strong>' . esc_html__( 're-login', 'defender-security' ) . '</strong></a>'
591 );
592 $message .= '<br/>';
593 $message .= esc_html__( 'This will auto reload now.', 'defender-security' );
594
595 $return['reload'] = 3;
596 $redirect = rawurlencode( network_admin_url( 'admin.php?page=wdf-setting&view=configs' ) );
597 if ( isset( $data['screen'] ) && 'dashboard' === $data['screen'] ) {
598 $redirect = rawurlencode( network_admin_url( 'admin.php?page=wp-defender' ) );
599 }
600 $return['redirect'] = add_query_arg(
601 'redirect_to',
602 $redirect,
603 $login_url
604 );
605 $return['interval'] = 2;
606 }
607
608 $return['message'] = $message;
609 $return['auto_close'] = true;
610 $return['configs'] = Config_Hub_Helper::get_fresh_frontend_configs( $this->service );
611
612 return new Response( true, $return );
613 }
614
615 /**
616 * Update config.
617 *
618 * @param Request $request The request object containing new config data.
619 *
620 * @return Response
621 * @defender_route
622 */
623 public function update_config( Request $request ) {
624 $data = $request->get_data();
625 $key = trim( $data['key'] );
626 $name = trim( $data['name'] );
627 $description = trim( $data['desc'] );
628 if ( empty( $name ) || empty( $key ) ) {
629 return new Response(
630 false,
631 array(
632 'message' => esc_html__( 'Invalid config', 'defender-security' ),
633 )
634 );
635 }
636
637 $config = get_site_option( $key );
638 if ( false === $config ) {
639 return new Response(
640 false,
641 array(
642 'message' => esc_html__( 'Invalid config', 'defender-security' ),
643 )
644 );
645 }
646
647 $old_config = $config;
648 $config['name'] = sanitize_text_field( $name );
649 $config['description'] = sanitize_textarea_field( $description );
650
651 // Check data has been changed or not.
652 if (
653 $old_config['name'] === $config['name'] &&
654 $old_config['description'] === $config['description']
655 ) {
656 // Data is not changed, so not need to run update query.
657 $option_updated = true;
658 } else {
659 $option_updated = update_site_option( $key, $config );
660 Config_Hub_Helper::update_on_hub( $config );
661 }
662
663 if ( $option_updated ) {
664 return new Response(
665 true,
666 array(
667 'message' => sprintf(
668 /* translators: %s: Config name. */
669 esc_html__( '%s config saved successfully.', 'defender-security' ),
670 '<strong>' . $name . '</strong>'
671 ),
672 'auto_close' => true,
673 'configs' => Config_Hub_Helper::get_fresh_frontend_configs( $this->service ),
674 )
675 );
676 } else {
677 return new Response(
678 false,
679 array(
680 'message' => esc_html__(
681 'An error occurred while saving your config. Please try it again.',
682 'defender-security'
683 ),
684 )
685 );
686 }
687 }
688
689 /**
690 * Delete config.
691 *
692 * @param Request $request The request object containing config key.
693 *
694 * @return Response
695 * @defender_route
696 */
697 public function delete_config( Request $request ) {
698 $data = $request->get_data();
699 $key = trim( $data['key'] );
700 if ( empty( $key ) ) {
701 return new Response(
702 false,
703 array(
704 'message' => esc_html__( 'Invalid config', 'defender-security' ),
705 )
706 );
707 }
708
709 $config = get_site_option( $key );
710 if ( isset( $config['is_removable'] ) && ! $config['is_removable'] ) {
711 return new Response(
712 false,
713 array(
714 'message' => esc_html__( 'Config can\'t be removed', 'defender-security' ),
715 )
716 );
717 }
718
719 // Remove from HUB.
720 if ( isset( $config['hub_id'] ) ) {
721 Config_Hub_Helper::delete_configs_from_hub( (int) $config['hub_id'] );
722 }
723
724 if ( 0 === strpos( $key, 'wp_defender_config' ) ) {
725 delete_site_option( $key );
726 $this->service->clear_keys();
727
728 return new Response(
729 true,
730 array(
731 'message' => esc_html__( 'Config removed successfully.', 'defender-security' ),
732 'auto_close' => true,
733 'configs' => Config_Hub_Helper::get_fresh_frontend_configs( $this->service ),
734 )
735 );
736 }
737
738 return new Response(
739 false,
740 array(
741 'message' => esc_html__( 'Invalid config', 'defender-security' ),
742 )
743 );
744 }
745
746 /**
747 * Exports strings.
748 *
749 * @return array An array of strings.
750 */
751 public function export_strings(): array {
752 return array();
753 }
754
755 /**
756 * Update config status and return them.
757 *
758 * @return array
759 */
760 private function get_configs_and_update_status() {
761 $configs = Config_Hub_Helper::get_configs( $this->service );
762 $is_remove = Config_Hub_Helper::check_remove_active_flag();
763
764 // Loop to update strings of configs.
765 foreach ( $configs as $key => &$config ) {
766 if ( ! is_array( $config ) ) {
767 continue;
768 }
769
770 $config['strings'] = $this->service->import_module_strings( $config );
771
772 if ( $is_remove ) {
773 $config['is_active'] = false;
774 }
775
776 // Update config data.
777 update_site_option( $key, $config );
778 }
779
780 return $configs;
781 }
782
783 /**
784 * Generates an error message for when there is an issue applying some tweaks from the Recommendations tab.
785 *
786 * @return Response The response object containing the error message and fresh frontend configurations.
787 */
788 private function apply_config_recommendations_error_message(): Response {
789 $message = sprintf(
790 /* translators: 1: Recommendations tab, 2: wp-config.php file, 3: Documentation. */
791 esc_html__(
792 '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.',
793 'defender-security'
794 ),
795 '<strong>' . esc_html__( 'Recommendations', 'defender-security' ) . '</strong>',
796 '<strong>' . esc_html__( 'wp-config.php', 'defender-security' ) . '</strong>',
797 '<a href="' . WP_DEFENDER_DOCS_LINK . '#manually-applying-recommendations" target="_blank">' . esc_html__( 'documentation', 'defender-security' ) . '</a>'
798 );
799
800 return new Response(
801 false,
802 array(
803 'message' => $message,
804 'configs' => Config_Hub_Helper::get_fresh_frontend_configs( $this->service ),
805 )
806 );
807 }
808
809 /**
810 * Check if the logger cron is scheduled to run.
811 *
812 * @return void
813 */
814 public function check_cron_schedule(): void {
815 if ( ! wp_next_scheduled( 'wp_defender_clear_logs' ) ) {
816 wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'wp_defender_clear_logs' );
817 }
818 }
819
820 /**
821 * Clear out lines that are older than 30 days.
822 *
823 * @return void
824 */
825 public function clear_logs(): void {
826 // since 2.7.0.
827 $time_limit = apply_filters( 'wpdef_clear_logs_time_limit', MONTH_IN_SECONDS );
828
829 if ( is_multisite() ) {
830 global $wpdb;
831 $offset = 0;
832 $limit = 100;
833 $blogs = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
834 $wpdb->prepare( "SELECT blog_id FROM {$wpdb->blogs} LIMIT %d, %d", $offset, $limit ),
835 ARRAY_A
836 );
837 while ( ! empty( $blogs ) && is_array( $blogs ) ) {
838 foreach ( $blogs as $blog ) {
839 switch_to_blog( $blog['blog_id'] );
840
841 $this->clear_logs_from_files( $time_limit );
842
843 restore_current_blog();
844 }
845 $offset += $limit;
846 $blogs = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
847 $wpdb->prepare( "SELECT blog_id FROM {$wpdb->blogs} LIMIT %d, %d", $offset, $limit ),
848 ARRAY_A
849 );
850 }
851 } else {
852 $this->clear_logs_from_files( $time_limit );
853 }
854 }
855
856 /**
857 * Clear log files older than the specified time.
858 *
859 * @param int $time_limit The time limit in seconds.
860 *
861 * @return void
862 * @since 2.7.0
863 */
864 public function clear_logs_from_files( int $time_limit = MONTH_IN_SECONDS ) {
865 global $wp_filesystem;
866 // Initialize the WP filesystem, no more using 'file-put-contents' function.
867 if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) {
868 require_once ABSPATH . '/wp-admin/includes/file.php';
869 WP_Filesystem();
870 }
871 $now = wp_date( 'c' );
872 $files = array( wd_internal_log() );
873
874 foreach ( $files as $file_name ) {
875 $file_path = $this->get_log_path( $file_name );
876
877 if ( ! file_exists( $file_path ) ) {
878 return;
879 }
880
881 $content = file( $file_path );
882 $size_of_content = is_array( $content ) || $content instanceof Countable ? count( $content ) : 0;
883
884 foreach ( $content as $index => $line ) {
885 // If the line does not start with '[' (it's probably not a new entry).
886 $first_char = substr( $line, 0, 1 );
887
888 if ( '[' !== $first_char ) {
889 // Delete.
890 unset( $content[ $index ] );
891 }
892
893 /**
894 * Get the date from entry. Items can be an array it two cases - if there's a valid date, or if the line
895 * contained something like [header] in the start. Cannot make assumptions just on the fact it's an array.
896 */
897 preg_match( '/\[(.*)\]/', $line, $items );
898
899 // If, for some reason, can't get the date, or it's not the size of an ISO 8601 date.
900 if ( ! isset( $items[1] ) || 25 !== strlen( $items[1] ) ) {
901 // Delete.
902 unset( $content[ $index ] );
903 } else {
904 // It looks like it's a valid date string, compare with today.
905 $time_diff = strtotime( $now ) - strtotime( $items[1] );
906
907 // 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.
908 if ( $time_diff < $time_limit ) {
909 break;
910 }
911
912 unset( $content[ $index ] );
913 }
914 }
915
916 // Nothing changed - do nothing.
917 if ( ( is_array( $content ) || $content instanceof Countable ? count( $content ) : 0 ) === $size_of_content ) {
918 return;
919 }
920
921 // Glue back together and write back to file.
922 $content = implode( '', $content );
923
924 $wp_filesystem->put_contents( $file_path, $content );
925 }
926 }
927
928 /**
929 * Track the data if there are settings changes.
930 *
931 * @param array $old_settings Old settings.
932 * @param array $new_settings New settings.
933 *
934 * @return void
935 * @since 4.2.0
936 */
937 public function intercept_settings_update( $old_settings, $new_settings ) {
938 $from = $this->get_triggered_location();
939 if (
940 '' !== $from
941 && isset( $new_settings['usage_tracking'], $old_settings['usage_tracking'] )
942 && $new_settings['usage_tracking'] !== $old_settings['usage_tracking']
943 ) {
944 $this->track_opt_toggle( ! empty( $new_settings['usage_tracking'] ), $from );
945 }
946 }
947 }
948