| @@ -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 | |
| @@ -98,16 +101,26 @@ | ||
| 98 | 101 | update_option('wp_debug_log_enabled', $wp_debug_log); |
| 99 | 102 | update_option('wp_debug_display_enabled', $wp_debug_display); |
| 100 | 103 | |
| 101 | 104 | $data = new ConfigControllerMetaSync(); |
| 102 | - $data->store(); | |
| 103 | - | |
| 104 | - add_settings_error( | |
| 105 | - 'metasync_messages', | |
| 106 | - 'metasync_message', | |
| 107 | - 'WordPress debug settings updated successfully.', | |
| 108 | - 'updated' | |
| 109 | - ); | |
| 105 | + $saved = $data->store(); | |
| 106 | + | |
| 107 | + if ($saved) { | |
| 108 | + add_settings_error( | |
| 109 | + 'metasync_messages', | |
| 110 | + 'metasync_message', | |
| 111 | + 'WordPress debug settings updated successfully.', | |
| 112 | + 'updated' | |
| 113 | + ); | |
| 114 | + } else { | |
| 115 | + add_settings_error( | |
| 116 | + 'metasync_messages', | |
| 117 | + 'metasync_message', | |
| 118 | + 'Debug settings were saved, but wp-config.php could not be updated: ' | |
| 119 | + . ($data->getConfigError() !== '' ? $data->getConfigError() : 'the file is not writable.'), | |
| 120 | + 'error' | |
| 121 | + ); | |
| 122 | + } | |
| 110 | 123 | } elseif ($can_manage_debug_settings && isset($_POST['wp_debug_nonce']) && !wp_verify_nonce($_POST['wp_debug_nonce'], 'metasync_wp_debug_settings')) { |
| 111 | 124 | add_settings_error( |
| 112 | 125 | 'metasync_messages', |
| 113 | 126 | 'metasync_message', |
| @@ -224,9 +237,9 @@ | ||
| 224 | 237 | * Update wp-config.php debug constants. |
| 225 | 238 | */ |
| 226 | 239 | public function metasync_update_wp_config() { |
| 227 | 240 | $wp_config_path = ABSPATH . 'wp-config.php'; |
| 228 | - if (file_exists($wp_config_path) && is_writable($wp_config_path)) { | |
| 241 | + if (file_exists($wp_config_path) && wp_is_writable($wp_config_path)) { | |
| 229 | 242 | $config_file = file_get_contents($wp_config_path); |
| 230 | 243 | |
| 231 | 244 | $wp_debug_enabled = get_option('wp_debug_enabled', 'false') === 'true' ? 'true' : 'false'; |
| 232 | 245 | if (preg_match("/define\s*\(\s*['\"]WP_DEBUG['\"]\s*,\s*.*?\s*\)\s*;/", $config_file)) { |
| @@ -248,8 +261,14 @@ | ||
| 248 | 261 | } else { |
| 249 | 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); |
| 250 | 263 | } |
| 251 | 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). | |
| 252 | 271 | file_put_contents($wp_config_path, $config_file); |
| 253 | 272 | } else { |
| 254 | 273 | wp_die('The wp-config.php file is not writable. Please check the file permissions.'); |
| 255 | 274 | } |
| @@ -381,9 +400,9 @@ | ||
| 381 | 400 | <!-- Debug Mode Controls --> |
| 382 | 401 | <div style="margin-bottom: 30px;"> |
| 383 | 402 | <h4 style="margin-top: 0; color: var(--dashboard-text-primary);">Controls</h4> |
| 384 | 403 | |
| 385 | - <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')); ?>"> | |
| 386 | 405 | <input type="hidden" name="metasync_debug_mode_action_advanced" value="1" /> |
| 387 | 406 | <?php wp_nonce_field('metasync_debug_mode_action_advanced', 'metasync_debug_mode_nonce_advanced'); ?> |
| 388 | 407 | |
| 389 | 408 | <?php if (!$status['enabled']): ?> |
| @@ -479,9 +498,9 @@ | ||
| 479 | 498 | } |
| 480 | 499 | }); |
| 481 | 500 | |
| 482 | 501 | <?php if ($status['enabled'] && !$status['indefinite']): ?> |
| 483 | - var initialTimeRemaining = <?php echo $status['time_remaining']; ?>; | |
| 502 | + var initialTimeRemaining = <?php echo (int) $status['time_remaining']; ?>; | |
| 484 | 503 | var hasReloaded = false; |
| 485 | 504 | |
| 486 | 505 | function updateDebugTimeRemaining() { |
| 487 | 506 | if (initialTimeRemaining <= 0 || hasReloaded) { |
| @@ -488,12 +507,12 @@ | ||
| 488 | 507 | return; |
| 489 | 508 | } |
| 490 | 509 | |
| 491 | 510 | $.ajax({ |
| 492 | - url: '<?php echo rest_url('metasync/v1/debug-mode/status'); ?>', | |
| 511 | + url: '<?php echo esc_url(rest_url('metasync/v1/debug-mode/status')); ?>', | |
| 493 | 512 | method: 'GET', |
| 494 | 513 | beforeSend: function(xhr) { |
| 495 | - 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')); ?>'); | |
| 496 | 515 | }, |
| 497 | 516 | success: function(response) { |
| 498 | 517 | console.log('MetaSync Debug Mode Status:', response); |
| 499 | 518 | |
| @@ -577,9 +596,9 @@ | ||
| 577 | 596 | <td style="padding: 10px 12px; color: var(--dashboard-text-secondary); font-size: 13px;"> |
| 578 | 597 | <?php |
| 579 | 598 | $last_seen = strtotime($error['last_seen']); |
| 580 | 599 | $time_diff = human_time_diff($last_seen, current_time('timestamp')); |
| 581 | - 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>'; | |
| 582 | 601 | ?> |
| 583 | 602 | </td> |
| 584 | 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']); ?>"> |
| 585 | 604 | <?php echo esc_html($error['message']); ?> |
| @@ -589,9 +608,9 @@ | ||
| 589 | 608 | </tbody> |
| 590 | 609 | </table> |
| 591 | 610 | </div> |
| 592 | 611 | |
| 593 | - <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;"> | |
| 594 | 613 | <input type="hidden" name="clear_error_summary" value="yes" /> |
| 595 | 614 | <?php wp_nonce_field('metasync_clear_error_summary_nonce', 'clear_error_summary_nonce'); ?> |
| 596 | 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;"> |
| 597 | 616 | <span class="dashicons dashicons-trash" style="margin-top:3px;font-size:15px;width:15px;height:15px;"></span> Clear Error Summary |
| @@ -623,9 +642,9 @@ | ||
| 623 | 642 | <div style="margin-bottom: 30px;"> |
| 624 | 643 | <h4 style="margin-top: 0; color: var(--dashboard-text-primary);">Clear Error Logs</h4> |
| 625 | 644 | <p style="margin-bottom: 15px; color: var(--dashboard-text-secondary);">Clear WordPress error logs to free up space and remove old entries.</p> |
| 626 | 645 | |
| 627 | - <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;"> | |
| 628 | 647 | <input type="hidden" name="clear_log" value="yes" /> |
| 629 | 648 | <?php wp_nonce_field('metasync_clear_log_nonce', 'clear_log_nonce'); ?> |
| 630 | 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)';"> |
| 631 | 650 | 🧹 Clear Error Logs |
| @@ -741,9 +760,9 @@ | ||
| 741 | 760 | { |
| 742 | 761 | if (isset($_POST['metasync_debug_mode_action_advanced'])) { |
| 743 | 762 | if (!wp_verify_nonce($_POST['metasync_debug_mode_nonce_advanced'], 'metasync_debug_mode_action_advanced')) { |
| 744 | 763 | $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&debug_error=1'); |
| 745 | - wp_redirect($redirect_url); | |
| 764 | + wp_safe_redirect($redirect_url); | |
| 746 | 765 | exit; |
| 747 | 766 | } |
| 748 | 767 | |
| 749 | 768 | if (!current_user_can('manage_options')) { |
| @@ -751,9 +770,9 @@ | ||
| 751 | 770 | } |
| 752 | 771 | |
| 753 | 772 | if (!class_exists('Metasync_Debug_Mode_Manager')) { |
| 754 | 773 | $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&debug_error=1&msg=manager_not_available'); |
| 755 | - wp_redirect($redirect_url); | |
| 774 | + wp_safe_redirect($redirect_url); | |
| 756 | 775 | exit; |
| 757 | 776 | } |
| 758 | 777 | |
| 759 | 778 | $debug_manager = Metasync_Debug_Mode_Manager::get_instance(); |
| @@ -790,9 +809,9 @@ | ||
| 790 | 809 | $redirect_url = add_query_arg('debug_error', '1', $redirect_url); |
| 791 | 810 | break; |
| 792 | 811 | } |
| 793 | 812 | |
| 794 | - wp_redirect($redirect_url); | |
| 813 | + wp_safe_redirect($redirect_url); | |
| 795 | 814 | exit; |
| 796 | 815 | } |
| 797 | 816 | } |
| 798 | 817 | |
| @@ -806,27 +825,30 @@ | ||
| 806 | 825 | } |
| 807 | 826 | |
| 808 | 827 | if (isset($_POST['clear_log'])) { |
| 809 | 828 | if (wp_verify_nonce($_POST['clear_log_nonce'], 'metasync_clear_log_nonce')) { |
| 810 | - $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'); | |
| 811 | 831 | |
| 812 | - if (file_exists($log_file)) { | |
| 832 | + if (false !== $log_file && file_exists($log_file)) { | |
| 813 | 833 | file_put_contents($log_file, ''); |
| 814 | 834 | |
| 815 | - $backup_files = glob(WP_CONTENT_DIR . '/metasync_data/plugin_errors.log.old.*'); | |
| 816 | - if ($backup_files) { | |
| 817 | - foreach ($backup_files as $backup_file) { | |
| 818 | - @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 | + } | |
| 819 | 841 | } |
| 820 | 842 | } |
| 821 | 843 | } |
| 822 | 844 | |
| 823 | 845 | $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&log_cleared=1'); |
| 824 | - wp_redirect($redirect_url); | |
| 846 | + wp_safe_redirect($redirect_url); | |
| 825 | 847 | exit; |
| 826 | 848 | } else { |
| 827 | 849 | $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&clear_error=1'); |
| 828 | - wp_redirect($redirect_url); | |
| 850 | + wp_safe_redirect($redirect_url); | |
| 829 | 851 | exit; |
| 830 | 852 | } |
| 831 | 853 | } |
| 832 | 854 | |
| @@ -834,18 +856,18 @@ | ||
| 834 | 856 | if (wp_verify_nonce($_POST['clear_error_summary_nonce'], 'metasync_clear_error_summary_nonce')) { |
| 835 | 857 | if (class_exists('Metasync_Error_Logger')) { |
| 836 | 858 | Metasync_Error_Logger::clear_error_summary(); |
| 837 | 859 | $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&error_summary_cleared=1'); |
| 838 | - wp_redirect($redirect_url); | |
| 860 | + wp_safe_redirect($redirect_url); | |
| 839 | 861 | exit; |
| 840 | 862 | } else { |
| 841 | 863 | $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&error_summary_error=1'); |
| 842 | - wp_redirect($redirect_url); | |
| 864 | + wp_safe_redirect($redirect_url); | |
| 843 | 865 | exit; |
| 844 | 866 | } |
| 845 | 867 | } else { |
| 846 | 868 | $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&error_summary_error=1'); |
| 847 | - wp_redirect($redirect_url); | |
| 869 | + wp_safe_redirect($redirect_url); | |
| 848 | 870 | exit; |
| 849 | 871 | } |
| 850 | 872 | } |
| 851 | 873 | } |
| @@ -880,8 +902,18 @@ | ||
| 880 | 902 | // Code Minification settings |
| 881 | 903 | 'metasync_code_minification', |
| 882 | 904 | ]; |
| 883 | 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 | + | |
| 884 | 916 | $cleared_count = 0; |
| 885 | 917 | foreach ($metasync_options_to_clear as $option_name) { |
| 886 | 918 | if (get_option($option_name) !== false) { |
| 887 | 919 | delete_option($option_name); |
| @@ -923,15 +955,41 @@ | ||
| 923 | 955 | 'options_cleared_count' => $cleared_count, |
| 924 | 956 | 'new_token_prefix' => substr($new_plugin_auth_token, 0, 8) . '...', |
| 925 | 957 | 'triggered_by' => 'settings_reset_action' |
| 926 | 958 | ), 'info'); |
| 927 | - | |
| 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 | + | |
| 928 | 986 | $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&settings_cleared=1'); |
| 929 | - wp_redirect($redirect_url); | |
| 987 | + wp_safe_redirect($redirect_url); | |
| 930 | 988 | exit; |
| 931 | 989 | } else { |
| 932 | 990 | $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&clear_settings_error=1'); |
| 933 | - wp_redirect($redirect_url); | |
| 991 | + wp_safe_redirect($redirect_url); | |
| 934 | 992 | exit; |
| 935 | 993 | } |
| 936 | 994 | } |
| 937 | 995 | } |
| @@ -946,9 +1004,9 @@ | ||
| 946 | 1004 | public function get_error_log_content() |
| 947 | 1005 | { |
| 948 | 1006 | $execution_time = $this->get_execution_setting('max_execution_time'); |
| 949 | 1007 | if (function_exists('set_time_limit')) { |
| 950 | - @set_time_limit($execution_time); | |
| 1008 | + @set_time_limit($execution_time); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- runtime PHP setting with no WordPress API | |
| 951 | 1009 | } |
| 952 | 1010 | |
| 953 | 1011 | $log_file = WP_CONTENT_DIR . '/debug.log'; |
| 954 | 1012 | |
| @@ -980,9 +1038,9 @@ | ||
| 980 | 1038 | public function get_log_tail($file_path, $lines = null) |
| 981 | 1039 | { |
| 982 | 1040 | $execution_time = $this->get_execution_setting('max_execution_time'); |
| 983 | 1041 | if (function_exists('set_time_limit')) { |
| 984 | - @set_time_limit($execution_time); | |
| 1042 | + @set_time_limit($execution_time); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- runtime PHP setting with no WordPress API | |
| 985 | 1043 | } |
| 986 | 1044 | |
| 987 | 1045 | if ($lines === null) { |
| 988 | 1046 | $lines = $this->get_execution_setting('log_batch_size'); |
| @@ -987,9 +1045,9 @@ | ||
| 987 | 1045 | if ($lines === null) { |
| 988 | 1046 | $lines = $this->get_execution_setting('log_batch_size'); |
| 989 | 1047 | } |
| 990 | 1048 | |
| 991 | - $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 | |
| 992 | 1050 | if (!$handle) { |
| 993 | 1051 | return false; |
| 994 | 1052 | } |
| 995 | 1053 | |
| @@ -1018,9 +1076,9 @@ | ||
| 1018 | 1076 | if (!empty($line) && $line_count < $lines) { |
| 1019 | 1077 | array_unshift($result_lines, strrev($line)); |
| 1020 | 1078 | } |
| 1021 | 1079 | |
| 1022 | - fclose($handle); | |
| 1080 | + fclose($handle); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- operates on a native stream handle | |
| 1023 | 1081 | |
| 1024 | 1082 | return implode("\n", $result_lines); |
| 1025 | 1083 | } |
| 1026 | 1084 | } |