| @@ -72,9 +72,12 @@ | ||
| 72 | 72 | * |
| 73 | 73 | * @param Metasync_Admin $admin The admin instance (needed for header/nav rendering). |
| 74 | 74 | */ |
| 75 | 75 | public function metasync_display_error_log($admin) { |
| 76 | - $log_file = WP_CONTENT_DIR . '/metasync_data/plugin_errors.log'; | |
| 76 | + $log_file = Metasync_Data_Store::file_path('plugin_errors.log'); | |
| 77 | + if (false === $log_file) { | |
| 78 | + $log_file = ''; | |
| 79 | + } | |
| 77 | 80 | if (!Metasync::current_user_has_plugin_access()) { |
| 78 | 81 | return; |
| 79 | 82 | } |
| 80 | 83 | |
| @@ -234,9 +237,9 @@ | ||
| 234 | 237 | * Update wp-config.php debug constants. |
| 235 | 238 | */ |
| 236 | 239 | public function metasync_update_wp_config() { |
| 237 | 240 | $wp_config_path = ABSPATH . 'wp-config.php'; |
| 238 | - if (file_exists($wp_config_path) && is_writable($wp_config_path)) { | |
| 241 | + if (file_exists($wp_config_path) && wp_is_writable($wp_config_path)) { | |
| 239 | 242 | $config_file = file_get_contents($wp_config_path); |
| 240 | 243 | |
| 241 | 244 | $wp_debug_enabled = get_option('wp_debug_enabled', 'false') === 'true' ? 'true' : 'false'; |
| 242 | 245 | if (preg_match("/define\s*\(\s*['\"]WP_DEBUG['\"]\s*,\s*.*?\s*\)\s*;/", $config_file)) { |
| @@ -258,8 +261,14 @@ | ||
| 258 | 261 | } else { |
| 259 | 262 | $config_file = str_replace("/* That's all, stop editing! Happy publishing. */", "define('WP_DEBUG_DISPLAY', $wp_debug_display_enabled);\n\n/* That's all, stop editing! Happy publishing. */", $config_file); |
| 260 | 263 | } |
| 261 | 264 | |
| 265 | + // ABSPATH is intentional and verified: the Debug Mode feature | |
| 266 | + // toggles WP_DEBUG constants in wp-config.php itself, the one | |
| 267 | + // file whose location ABSPATH exists to describe. It is not a | |
| 268 | + // plugin-directory or generated-content write, so it stays here | |
| 269 | + // (Plugin Check's ABSPATHDetected warning is a false positive — | |
| 270 | + // wp-config.php cannot live in the uploads directory). | |
| 262 | 271 | file_put_contents($wp_config_path, $config_file); |
| 263 | 272 | } else { |
| 264 | 273 | wp_die('The wp-config.php file is not writable. Please check the file permissions.'); |
| 265 | 274 | } |
| @@ -391,9 +400,9 @@ | ||
| 391 | 400 | <!-- Debug Mode Controls --> |
| 392 | 401 | <div style="margin-bottom: 30px;"> |
| 393 | 402 | <h4 style="margin-top: 0; color: var(--dashboard-text-primary);">Controls</h4> |
| 394 | 403 | |
| 395 | - <form method="post" action="<?php echo admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced'); ?>"> | |
| 404 | + <form method="post" action="<?php echo esc_url(admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced')); ?>"> | |
| 396 | 405 | <input type="hidden" name="metasync_debug_mode_action_advanced" value="1" /> |
| 397 | 406 | <?php wp_nonce_field('metasync_debug_mode_action_advanced', 'metasync_debug_mode_nonce_advanced'); ?> |
| 398 | 407 | |
| 399 | 408 | <?php if (!$status['enabled']): ?> |
| @@ -489,9 +498,9 @@ | ||
| 489 | 498 | } |
| 490 | 499 | }); |
| 491 | 500 | |
| 492 | 501 | <?php if ($status['enabled'] && !$status['indefinite']): ?> |
| 493 | - var initialTimeRemaining = <?php echo $status['time_remaining']; ?>; | |
| 502 | + var initialTimeRemaining = <?php echo (int) $status['time_remaining']; ?>; | |
| 494 | 503 | var hasReloaded = false; |
| 495 | 504 | |
| 496 | 505 | function updateDebugTimeRemaining() { |
| 497 | 506 | if (initialTimeRemaining <= 0 || hasReloaded) { |
| @@ -498,12 +507,12 @@ | ||
| 498 | 507 | return; |
| 499 | 508 | } |
| 500 | 509 | |
| 501 | 510 | $.ajax({ |
| 502 | - url: '<?php echo rest_url('metasync/v1/debug-mode/status'); ?>', | |
| 511 | + url: '<?php echo esc_url(rest_url('metasync/v1/debug-mode/status')); ?>', | |
| 503 | 512 | method: 'GET', |
| 504 | 513 | beforeSend: function(xhr) { |
| 505 | - xhr.setRequestHeader('X-WP-Nonce', '<?php echo wp_create_nonce('wp_rest'); ?>'); | |
| 514 | + xhr.setRequestHeader('X-WP-Nonce', '<?php echo esc_attr(wp_create_nonce('wp_rest')); ?>'); | |
| 506 | 515 | }, |
| 507 | 516 | success: function(response) { |
| 508 | 517 | console.log('MetaSync Debug Mode Status:', response); |
| 509 | 518 | |
| @@ -587,9 +596,9 @@ | ||
| 587 | 596 | <td style="padding: 10px 12px; color: var(--dashboard-text-secondary); font-size: 13px;"> |
| 588 | 597 | <?php |
| 589 | 598 | $last_seen = strtotime($error['last_seen']); |
| 590 | 599 | $time_diff = human_time_diff($last_seen, current_time('timestamp')); |
| 591 | - echo esc_html($error['last_seen']) . ' <span style="color: var(--dashboard-text-secondary);">(' . $time_diff . ' ago)</span>'; | |
| 600 | + echo esc_html($error['last_seen']) . ' <span style="color: var(--dashboard-text-secondary);">(' . esc_html($time_diff) . ' ago)</span>'; | |
| 592 | 601 | ?> |
| 593 | 602 | </td> |
| 594 | 603 | <td style="padding: 10px 12px; color: var(--dashboard-text-primary); max-width: 400px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" title="<?php echo esc_attr($error['message']); ?>"> |
| 595 | 604 | <?php echo esc_html($error['message']); ?> |
| @@ -599,9 +608,9 @@ | ||
| 599 | 608 | </tbody> |
| 600 | 609 | </table> |
| 601 | 610 | </div> |
| 602 | 611 | |
| 603 | - <form method="post" action="<?php echo admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced'); ?>" style="margin-bottom: 20px;"> | |
| 612 | + <form method="post" action="<?php echo esc_url(admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced')); ?>" style="margin-bottom: 20px;"> | |
| 604 | 613 | <input type="hidden" name="clear_error_summary" value="yes" /> |
| 605 | 614 | <?php wp_nonce_field('metasync_clear_error_summary_nonce', 'clear_error_summary_nonce'); ?> |
| 606 | 615 | <button type="submit" class="button button-secondary" style="background: #dc3232; color: #ffffff; border: none; padding: 8px 16px; border-radius: 4px; font-weight: 500; cursor: pointer;"> |
| 607 | 616 | <span class="dashicons dashicons-trash" style="margin-top:3px;font-size:15px;width:15px;height:15px;"></span> Clear Error Summary |
| @@ -633,9 +642,9 @@ | ||
| 633 | 642 | <div style="margin-bottom: 30px;"> |
| 634 | 643 | <h4 style="margin-top: 0; color: var(--dashboard-text-primary);">Clear Error Logs</h4> |
| 635 | 644 | <p style="margin-bottom: 15px; color: var(--dashboard-text-secondary);">Clear WordPress error logs to free up space and remove old entries.</p> |
| 636 | 645 | |
| 637 | - <form method="post" action="<?php echo admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced'); ?>" style="margin-bottom: 20px;"> | |
| 646 | + <form method="post" action="<?php echo esc_url(admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced')); ?>" style="margin-bottom: 20px;"> | |
| 638 | 647 | <input type="hidden" name="clear_log" value="yes" /> |
| 639 | 648 | <?php wp_nonce_field('metasync_clear_log_nonce', 'clear_log_nonce'); ?> |
| 640 | 649 | <button type="submit" class="metasync-btn-primary" style="background: var(--dashboard-gradient-primary); color: #ffffff; border: none; padding: 10px 20px; border-radius: 8px; font-weight: 500; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); display: inline-block; width: auto; min-width: 240px; max-width: fit-content;" onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 4px 8px rgba(0, 0, 0, 0.15)';" onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='0 2px 4px rgba(0, 0, 0, 0.1)';"> |
| 641 | 650 | 🧹 Clear Error Logs |
| @@ -751,9 +760,9 @@ | ||
| 751 | 760 | { |
| 752 | 761 | if (isset($_POST['metasync_debug_mode_action_advanced'])) { |
| 753 | 762 | if (!wp_verify_nonce($_POST['metasync_debug_mode_nonce_advanced'], 'metasync_debug_mode_action_advanced')) { |
| 754 | 763 | $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&debug_error=1'); |
| 755 | - wp_redirect($redirect_url); | |
| 764 | + wp_safe_redirect($redirect_url); | |
| 756 | 765 | exit; |
| 757 | 766 | } |
| 758 | 767 | |
| 759 | 768 | if (!current_user_can('manage_options')) { |
| @@ -761,9 +770,9 @@ | ||
| 761 | 770 | } |
| 762 | 771 | |
| 763 | 772 | if (!class_exists('Metasync_Debug_Mode_Manager')) { |
| 764 | 773 | $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&debug_error=1&msg=manager_not_available'); |
| 765 | - wp_redirect($redirect_url); | |
| 774 | + wp_safe_redirect($redirect_url); | |
| 766 | 775 | exit; |
| 767 | 776 | } |
| 768 | 777 | |
| 769 | 778 | $debug_manager = Metasync_Debug_Mode_Manager::get_instance(); |
| @@ -800,9 +809,9 @@ | ||
| 800 | 809 | $redirect_url = add_query_arg('debug_error', '1', $redirect_url); |
| 801 | 810 | break; |
| 802 | 811 | } |
| 803 | 812 | |
| 804 | - wp_redirect($redirect_url); | |
| 813 | + wp_safe_redirect($redirect_url); | |
| 805 | 814 | exit; |
| 806 | 815 | } |
| 807 | 816 | } |
| 808 | 817 | |
| @@ -816,27 +825,30 @@ | ||
| 816 | 825 | } |
| 817 | 826 | |
| 818 | 827 | if (isset($_POST['clear_log'])) { |
| 819 | 828 | if (wp_verify_nonce($_POST['clear_log_nonce'], 'metasync_clear_log_nonce')) { |
| 820 | - $log_file = WP_CONTENT_DIR . '/metasync_data/plugin_errors.log'; | |
| 829 | + $data_dir = Metasync_Data_Store::base_dir(); | |
| 830 | + $log_file = Metasync_Data_Store::file_path('plugin_errors.log'); | |
| 821 | 831 | |
| 822 | - if (file_exists($log_file)) { | |
| 832 | + if (false !== $log_file && file_exists($log_file)) { | |
| 823 | 833 | file_put_contents($log_file, ''); |
| 824 | 834 | |
| 825 | - $backup_files = glob(WP_CONTENT_DIR . '/metasync_data/plugin_errors.log.old.*'); | |
| 826 | - if ($backup_files) { | |
| 827 | - foreach ($backup_files as $backup_file) { | |
| 828 | - @unlink($backup_file); | |
| 835 | + if (false !== $data_dir) { | |
| 836 | + $backup_files = glob($data_dir . '/plugin_errors.log.old.*'); | |
| 837 | + if ($backup_files) { | |
| 838 | + foreach ($backup_files as $backup_file) { | |
| 839 | + @wp_delete_file($backup_file); | |
| 840 | + } | |
| 829 | 841 | } |
| 830 | 842 | } |
| 831 | 843 | } |
| 832 | 844 | |
| 833 | 845 | $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&log_cleared=1'); |
| 834 | - wp_redirect($redirect_url); | |
| 846 | + wp_safe_redirect($redirect_url); | |
| 835 | 847 | exit; |
| 836 | 848 | } else { |
| 837 | 849 | $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&clear_error=1'); |
| 838 | - wp_redirect($redirect_url); | |
| 850 | + wp_safe_redirect($redirect_url); | |
| 839 | 851 | exit; |
| 840 | 852 | } |
| 841 | 853 | } |
| 842 | 854 | |
| @@ -844,18 +856,18 @@ | ||
| 844 | 856 | if (wp_verify_nonce($_POST['clear_error_summary_nonce'], 'metasync_clear_error_summary_nonce')) { |
| 845 | 857 | if (class_exists('Metasync_Error_Logger')) { |
| 846 | 858 | Metasync_Error_Logger::clear_error_summary(); |
| 847 | 859 | $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&error_summary_cleared=1'); |
| 848 | - wp_redirect($redirect_url); | |
| 860 | + wp_safe_redirect($redirect_url); | |
| 849 | 861 | exit; |
| 850 | 862 | } else { |
| 851 | 863 | $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&error_summary_error=1'); |
| 852 | - wp_redirect($redirect_url); | |
| 864 | + wp_safe_redirect($redirect_url); | |
| 853 | 865 | exit; |
| 854 | 866 | } |
| 855 | 867 | } else { |
| 856 | 868 | $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&error_summary_error=1'); |
| 857 | - wp_redirect($redirect_url); | |
| 869 | + wp_safe_redirect($redirect_url); | |
| 858 | 870 | exit; |
| 859 | 871 | } |
| 860 | 872 | } |
| 861 | 873 | } |
| @@ -890,8 +902,18 @@ | ||
| 890 | 902 | // Code Minification settings |
| 891 | 903 | 'metasync_code_minification', |
| 892 | 904 | ]; |
| 893 | 905 | |
| 906 | + // Capture the complete white-label identity BEFORE the | |
| 907 | + // destructive clear so it can be restored afterwards. The | |
| 908 | + // reset is meant to remove operational data and credentials, | |
| 909 | + // not the reseller's public branding, logos, domains, custom | |
| 910 | + // OTTO name, visibility/access controls, or recovery settings. | |
| 911 | + if (!class_exists('Metasync_Whitelabel_Preservation')) { | |
| 912 | + require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-metasync-whitelabel-preservation.php'; | |
| 913 | + } | |
| 914 | + $whitelabel_state = Metasync_Whitelabel_Preservation::extract_whitelabel_state(); | |
| 915 | + | |
| 894 | 916 | $cleared_count = 0; |
| 895 | 917 | foreach ($metasync_options_to_clear as $option_name) { |
| 896 | 918 | if (get_option($option_name) !== false) { |
| 897 | 919 | delete_option($option_name); |
| @@ -933,15 +955,41 @@ | ||
| 933 | 955 | 'options_cleared_count' => $cleared_count, |
| 934 | 956 | 'new_token_prefix' => substr($new_plugin_auth_token, 0, 8) . '...', |
| 935 | 957 | 'triggered_by' => 'settings_reset_action' |
| 936 | 958 | ), 'info'); |
| 937 | - | |
| 959 | + | |
| 960 | + // Restore the preserved white-label identity over the freshly | |
| 961 | + // cleared options. A failed restore must not report success. | |
| 962 | + $restored = Metasync_Whitelabel_Preservation::restore_whitelabel_state($whitelabel_state); | |
| 963 | + if (!$restored) { | |
| 964 | + $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&clear_settings_error=1'); | |
| 965 | + wp_redirect($redirect_url); | |
| 966 | + exit; | |
| 967 | + } | |
| 968 | + | |
| 969 | + // Re-synchronise the branded plugin file headers so the | |
| 970 | + // WordPress plugin list and the plugin UI stay branded after | |
| 971 | + // the reset. Only attempted when white-label branding exists, | |
| 972 | + // and a failed sync is logged as a warning, not reported as a | |
| 973 | + // reset failure: the reset itself (operational clear + fresh | |
| 974 | + // token + branding restore) has already succeeded at this | |
| 975 | + // point, and hosts with read-only plugin files would otherwise | |
| 976 | + // never see a successful reset. | |
| 977 | + if (!empty($whitelabel_state['whitelabel']) || !empty($whitelabel_state['general'])) { | |
| 978 | + if (!class_exists('Metasync_Activator')) { | |
| 979 | + require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-metasync-activator.php'; | |
| 980 | + } | |
| 981 | + if (!Metasync_Activator::sync_plugin_file_headers()) { | |
| 982 | + error_log('MetaSync: failed to re-synchronise branded plugin file headers after the settings reset.'); | |
| 983 | + } | |
| 984 | + } | |
| 985 | + | |
| 938 | 986 | $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&settings_cleared=1'); |
| 939 | - wp_redirect($redirect_url); | |
| 987 | + wp_safe_redirect($redirect_url); | |
| 940 | 988 | exit; |
| 941 | 989 | } else { |
| 942 | 990 | $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&clear_settings_error=1'); |
| 943 | - wp_redirect($redirect_url); | |
| 991 | + wp_safe_redirect($redirect_url); | |
| 944 | 992 | exit; |
| 945 | 993 | } |
| 946 | 994 | } |
| 947 | 995 | } |
| @@ -956,9 +1004,9 @@ | ||
| 956 | 1004 | public function get_error_log_content() |
| 957 | 1005 | { |
| 958 | 1006 | $execution_time = $this->get_execution_setting('max_execution_time'); |
| 959 | 1007 | if (function_exists('set_time_limit')) { |
| 960 | - @set_time_limit($execution_time); | |
| 1008 | + @set_time_limit($execution_time); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- runtime PHP setting with no WordPress API | |
| 961 | 1009 | } |
| 962 | 1010 | |
| 963 | 1011 | $log_file = WP_CONTENT_DIR . '/debug.log'; |
| 964 | 1012 | |
| @@ -990,9 +1038,9 @@ | ||
| 990 | 1038 | public function get_log_tail($file_path, $lines = null) |
| 991 | 1039 | { |
| 992 | 1040 | $execution_time = $this->get_execution_setting('max_execution_time'); |
| 993 | 1041 | if (function_exists('set_time_limit')) { |
| 994 | - @set_time_limit($execution_time); | |
| 1042 | + @set_time_limit($execution_time); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- runtime PHP setting with no WordPress API | |
| 995 | 1043 | } |
| 996 | 1044 | |
| 997 | 1045 | if ($lines === null) { |
| 998 | 1046 | $lines = $this->get_execution_setting('log_batch_size'); |
| @@ -997,9 +1045,9 @@ | ||
| 997 | 1045 | if ($lines === null) { |
| 998 | 1046 | $lines = $this->get_execution_setting('log_batch_size'); |
| 999 | 1047 | } |
| 1000 | 1048 | |
| 1001 | - $handle = fopen($file_path, 'r'); | |
| 1049 | + $handle = fopen($file_path, 'r'); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- streaming file I/O; WP_Filesystem cannot return a raw stream handle | |
| 1002 | 1050 | if (!$handle) { |
| 1003 | 1051 | return false; |
| 1004 | 1052 | } |
| 1005 | 1053 | |
| @@ -1028,9 +1076,9 @@ | ||
| 1028 | 1076 | if (!empty($line) && $line_count < $lines) { |
| 1029 | 1077 | array_unshift($result_lines, strrev($line)); |
| 1030 | 1078 | } |
| 1031 | 1079 | |
| 1032 | - fclose($handle); | |
| 1080 | + fclose($handle); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- operates on a native stream handle | |
| 1033 | 1081 | |
| 1034 | 1082 | return implode("\n", $result_lines); |
| 1035 | 1083 | } |
| 1036 | 1084 | } |