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

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