| @@ -72,14 +72,23 @@ | ||
| 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 | } |
| 83 | + | |
| 84 | + $can_manage_debug_settings = current_user_can('manage_options'); | |
| 85 | + if (isset($_POST['wp_debug_nonce']) && !$can_manage_debug_settings) { | |
| 86 | + return; | |
| 87 | + } | |
| 80 | 88 | |
| 81 | - if (isset($_POST['wp_debug_log_enabled']) && | |
| 89 | + if ($can_manage_debug_settings && | |
| 90 | + isset($_POST['wp_debug_log_enabled']) && | |
| 82 | 91 | isset($_POST['wp_debug_enabled']) && |
| 83 | 92 | isset($_POST['wp_debug_display_enabled']) && |
| 84 | 93 | isset($_POST['wp_debug_nonce']) && |
| 85 | 94 | wp_verify_nonce($_POST['wp_debug_nonce'], 'metasync_wp_debug_settings')) { |
| @@ -92,20 +101,30 @@ | ||
| 92 | 101 | update_option('wp_debug_log_enabled', $wp_debug_log); |
| 93 | 102 | update_option('wp_debug_display_enabled', $wp_debug_display); |
| 94 | 103 | |
| 95 | 104 | $data = new ConfigControllerMetaSync(); |
| 96 | - $data->store(); | |
| 97 | - | |
| 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 | + } | |
| 123 | + } elseif ($can_manage_debug_settings && isset($_POST['wp_debug_nonce']) && !wp_verify_nonce($_POST['wp_debug_nonce'], 'metasync_wp_debug_settings')) { | |
| 98 | 124 | add_settings_error( |
| 99 | 125 | 'metasync_messages', |
| 100 | 126 | 'metasync_message', |
| 101 | - 'WordPress debug settings updated successfully.', | |
| 102 | - 'updated' | |
| 103 | - ); | |
| 104 | - } elseif (isset($_POST['wp_debug_nonce']) && !wp_verify_nonce($_POST['wp_debug_nonce'], 'metasync_wp_debug_settings')) { | |
| 105 | - add_settings_error( | |
| 106 | - 'metasync_messages', | |
| 107 | - 'metasync_message', | |
| 108 | 127 | 'Security verification failed. Please try again.', |
| 109 | 128 | 'error' |
| 110 | 129 | ); |
| 111 | 130 | } |
| @@ -218,9 +237,9 @@ | ||
| 218 | 237 | * Update wp-config.php debug constants. |
| 219 | 238 | */ |
| 220 | 239 | public function metasync_update_wp_config() { |
| 221 | 240 | $wp_config_path = ABSPATH . 'wp-config.php'; |
| 222 | - if (file_exists($wp_config_path) && is_writable($wp_config_path)) { | |
| 241 | + if (file_exists($wp_config_path) && wp_is_writable($wp_config_path)) { | |
| 223 | 242 | $config_file = file_get_contents($wp_config_path); |
| 224 | 243 | |
| 225 | 244 | $wp_debug_enabled = get_option('wp_debug_enabled', 'false') === 'true' ? 'true' : 'false'; |
| 226 | 245 | if (preg_match("/define\s*\(\s*['\"]WP_DEBUG['\"]\s*,\s*.*?\s*\)\s*;/", $config_file)) { |
| @@ -242,8 +261,14 @@ | ||
| 242 | 261 | } else { |
| 243 | 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); |
| 244 | 263 | } |
| 245 | 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). | |
| 246 | 271 | file_put_contents($wp_config_path, $config_file); |
| 247 | 272 | } else { |
| 248 | 273 | wp_die('The wp-config.php file is not writable. Please check the file permissions.'); |
| 249 | 274 | } |
| @@ -280,9 +305,15 @@ | ||
| 280 | 305 | } |
| 281 | 306 | } |
| 282 | 307 | |
| 283 | 308 | if ($changed) { |
| 284 | - require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-metasync-activator.php'; | |
| 309 | + if (!current_user_can('manage_options')) { | |
| 310 | + return; | |
| 311 | + } | |
| 312 | + | |
| 313 | + if (!class_exists('Metasync_Activator')) { | |
| 314 | + require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-metasync-activator.php'; | |
| 315 | + } | |
| 285 | 316 | Metasync_Activator::sync_plugin_file_headers(); |
| 286 | 317 | } |
| 287 | 318 | } |
| 288 | 319 | |
| @@ -310,9 +341,9 @@ | ||
| 310 | 341 | $status = $debug_manager->get_status(); |
| 311 | 342 | ?> |
| 312 | 343 | |
| 313 | 344 | <!-- Debug Mode Status Overview --> |
| 314 | - <div style="margin-bottom: 30px;"> | |
| 345 | + <div style="margin-bottom: 30px; padding-top: 20px;"> | |
| 315 | 346 | <h4 style="margin-top: 0; color: var(--dashboard-text-primary);">Current Status</h4> |
| 316 | 347 | <div style="background: rgba(255, 255, 255, 0.05); border: 1px solid var(--dashboard-border); padding: 20px; border-radius: 8px;"> |
| 317 | 348 | <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px;"> |
| 318 | 349 | <!-- Status Badge --> |
| @@ -523,15 +554,15 @@ | ||
| 523 | 554 | public function render_error_log_content() |
| 524 | 555 | { |
| 525 | 556 | ?> |
| 526 | 557 | <!-- Error Summary Section --> |
| 527 | - <div style="margin-bottom: 30px;"> | |
| 558 | + <div style="margin-bottom: 30px; padding-top: 20px;"> | |
| 528 | 559 | <h4 style="margin-top: 0; color: var(--dashboard-text-primary);">Error Summary</h4> |
| 529 | 560 | <p style="margin-bottom: 15px; color: var(--dashboard-text-secondary);">View categorized error statistics with counts and last occurrence times.</p> |
| 530 | 561 | |
| 531 | 562 | <?php |
| 532 | 563 | if (class_exists('Metasync_Error_Logger')) { |
| 533 | - $error_summary = Metasync_Error_Logger::get_error_summary(); | |
| 564 | + $error_summary = Metasync_Error_Logger::get_visible_error_summary(); | |
| 534 | 565 | |
| 535 | 566 | if (!empty($error_summary) && is_array($error_summary)) { |
| 536 | 567 | uasort($error_summary, function($a, $b) { |
| 537 | 568 | return strtotime($b['last_seen']) - strtotime($a['last_seen']); |
| @@ -551,9 +582,9 @@ | ||
| 551 | 582 | <tbody> |
| 552 | 583 | <?php foreach ($error_summary as $key => $error): ?> |
| 553 | 584 | <tr> |
| 554 | 585 | <td style="padding: 10px 12px; color: var(--dashboard-text-primary);"> |
| 555 | - <strong><?php echo esc_html($error['category']); ?></strong> | |
| 586 | + <strong><?php echo esc_html(Metasync_Error_Logger::get_display_label($error['category'])); ?></strong> | |
| 556 | 587 | </td> |
| 557 | 588 | <td style="padding: 10px 12px; text-align: center; color: var(--dashboard-text-secondary); font-family: monospace;"> |
| 558 | 589 | <code style="background: rgba(255, 255, 255, 0.1); padding: 2px 6px; border-radius: 3px;"><?php echo esc_html($error['code']); ?></code> |
| 559 | 590 | </td> |
| @@ -645,9 +676,9 @@ | ||
| 645 | 676 | <p style="color: var(--dashboard-accent);">💡 To enable error logging, add these lines to your wp-config.php file:</p> |
| 646 | 677 | <div class="metasync-copy-wrap"><pre class="metasync-copy-snippet" style="background: rgba(255, 255, 255, 0.05); border: 1px solid var(--dashboard-border); padding: 10px; border-radius: 4px; color: var(--dashboard-text-primary);">define('WP_DEBUG', true); |
| 647 | 678 | define('WP_DEBUG_LOG', true); |
| 648 | 679 | define('WP_DEBUG_DISPLAY', false);</pre><span class="metasync-copy-badge">Copy</span></div> |
| 649 | - <?php /* WP-492: click-to-copy for the wp-config snippet above */ ?> | |
| 680 | + <?php /* click-to-copy for the wp-config snippet above */ ?> | |
| 650 | 681 | <style> |
| 651 | 682 | .metasync-copy-wrap { position: relative; } |
| 652 | 683 | .metasync-copy-snippet { cursor: pointer; outline: 1px solid transparent; transition: outline-color .15s ease; } |
| 653 | 684 | .metasync-copy-wrap:hover .metasync-copy-snippet { outline-color: var(--dashboard-border, #374151); } |
| @@ -729,9 +760,9 @@ | ||
| 729 | 760 | { |
| 730 | 761 | if (isset($_POST['metasync_debug_mode_action_advanced'])) { |
| 731 | 762 | if (!wp_verify_nonce($_POST['metasync_debug_mode_nonce_advanced'], 'metasync_debug_mode_action_advanced')) { |
| 732 | 763 | $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&debug_error=1'); |
| 733 | - wp_redirect($redirect_url); | |
| 764 | + wp_safe_redirect($redirect_url); | |
| 734 | 765 | exit; |
| 735 | 766 | } |
| 736 | 767 | |
| 737 | 768 | if (!current_user_can('manage_options')) { |
| @@ -739,9 +770,9 @@ | ||
| 739 | 770 | } |
| 740 | 771 | |
| 741 | 772 | if (!class_exists('Metasync_Debug_Mode_Manager')) { |
| 742 | 773 | $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&debug_error=1&msg=manager_not_available'); |
| 743 | - wp_redirect($redirect_url); | |
| 774 | + wp_safe_redirect($redirect_url); | |
| 744 | 775 | exit; |
| 745 | 776 | } |
| 746 | 777 | |
| 747 | 778 | $debug_manager = Metasync_Debug_Mode_Manager::get_instance(); |
| @@ -778,9 +809,9 @@ | ||
| 778 | 809 | $redirect_url = add_query_arg('debug_error', '1', $redirect_url); |
| 779 | 810 | break; |
| 780 | 811 | } |
| 781 | 812 | |
| 782 | - wp_redirect($redirect_url); | |
| 813 | + wp_safe_redirect($redirect_url); | |
| 783 | 814 | exit; |
| 784 | 815 | } |
| 785 | 816 | } |
| 786 | 817 | |
| @@ -788,29 +819,36 @@ | ||
| 788 | 819 | * Handle error log operations (clear). |
| 789 | 820 | */ |
| 790 | 821 | public function handle_error_log_operations() |
| 791 | 822 | { |
| 823 | + if ((isset($_POST['clear_log']) || isset($_POST['clear_error_summary'])) && !current_user_can('manage_options')) { | |
| 824 | + return; | |
| 825 | + } | |
| 826 | + | |
| 792 | 827 | if (isset($_POST['clear_log'])) { |
| 793 | 828 | if (wp_verify_nonce($_POST['clear_log_nonce'], 'metasync_clear_log_nonce')) { |
| 794 | - $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'); | |
| 795 | 831 | |
| 796 | - if (file_exists($log_file)) { | |
| 832 | + if (false !== $log_file && file_exists($log_file)) { | |
| 797 | 833 | file_put_contents($log_file, ''); |
| 798 | 834 | |
| 799 | - $backup_files = glob(WP_CONTENT_DIR . '/metasync_data/plugin_errors.log.old.*'); | |
| 800 | - if ($backup_files) { | |
| 801 | - foreach ($backup_files as $backup_file) { | |
| 802 | - @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 | + } | |
| 803 | 841 | } |
| 804 | 842 | } |
| 805 | 843 | } |
| 806 | 844 | |
| 807 | 845 | $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&log_cleared=1'); |
| 808 | - wp_redirect($redirect_url); | |
| 846 | + wp_safe_redirect($redirect_url); | |
| 809 | 847 | exit; |
| 810 | 848 | } else { |
| 811 | 849 | $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&clear_error=1'); |
| 812 | - wp_redirect($redirect_url); | |
| 850 | + wp_safe_redirect($redirect_url); | |
| 813 | 851 | exit; |
| 814 | 852 | } |
| 815 | 853 | } |
| 816 | 854 | |
| @@ -818,18 +856,18 @@ | ||
| 818 | 856 | if (wp_verify_nonce($_POST['clear_error_summary_nonce'], 'metasync_clear_error_summary_nonce')) { |
| 819 | 857 | if (class_exists('Metasync_Error_Logger')) { |
| 820 | 858 | Metasync_Error_Logger::clear_error_summary(); |
| 821 | 859 | $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&error_summary_cleared=1'); |
| 822 | - wp_redirect($redirect_url); | |
| 860 | + wp_safe_redirect($redirect_url); | |
| 823 | 861 | exit; |
| 824 | 862 | } else { |
| 825 | 863 | $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&error_summary_error=1'); |
| 826 | - wp_redirect($redirect_url); | |
| 864 | + wp_safe_redirect($redirect_url); | |
| 827 | 865 | exit; |
| 828 | 866 | } |
| 829 | 867 | } else { |
| 830 | 868 | $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&error_summary_error=1'); |
| 831 | - wp_redirect($redirect_url); | |
| 869 | + wp_safe_redirect($redirect_url); | |
| 832 | 870 | exit; |
| 833 | 871 | } |
| 834 | 872 | } |
| 835 | 873 | } |
| @@ -838,8 +876,12 @@ | ||
| 838 | 876 | * Handle clear all settings operations. |
| 839 | 877 | */ |
| 840 | 878 | public function handle_clear_all_settings() |
| 841 | 879 | { |
| 880 | + if (isset($_POST['clear_all_settings']) && !current_user_can('manage_options')) { | |
| 881 | + return; | |
| 882 | + } | |
| 883 | + | |
| 842 | 884 | if (isset($_POST['clear_all_settings'])) { |
| 843 | 885 | if (wp_verify_nonce($_POST['clear_all_settings_nonce'], 'metasync_clear_all_settings_nonce')) { |
| 844 | 886 | |
| 845 | 887 | $metasync_options_to_clear = [ |
| @@ -905,13 +947,13 @@ | ||
| 905 | 947 | 'triggered_by' => 'settings_reset_action' |
| 906 | 948 | ), 'info'); |
| 907 | 949 | |
| 908 | 950 | $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&settings_cleared=1'); |
| 909 | - wp_redirect($redirect_url); | |
| 951 | + wp_safe_redirect($redirect_url); | |
| 910 | 952 | exit; |
| 911 | 953 | } else { |
| 912 | 954 | $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&clear_settings_error=1'); |
| 913 | - wp_redirect($redirect_url); | |
| 955 | + wp_safe_redirect($redirect_url); | |
| 914 | 956 | exit; |
| 915 | 957 | } |
| 916 | 958 | } |
| 917 | 959 | } |
| @@ -926,9 +968,9 @@ | ||
| 926 | 968 | public function get_error_log_content() |
| 927 | 969 | { |
| 928 | 970 | $execution_time = $this->get_execution_setting('max_execution_time'); |
| 929 | 971 | if (function_exists('set_time_limit')) { |
| 930 | - @set_time_limit($execution_time); | |
| 972 | + @set_time_limit($execution_time); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- runtime PHP setting with no WordPress API | |
| 931 | 973 | } |
| 932 | 974 | |
| 933 | 975 | $log_file = WP_CONTENT_DIR . '/debug.log'; |
| 934 | 976 | |
| @@ -960,9 +1002,9 @@ | ||
| 960 | 1002 | public function get_log_tail($file_path, $lines = null) |
| 961 | 1003 | { |
| 962 | 1004 | $execution_time = $this->get_execution_setting('max_execution_time'); |
| 963 | 1005 | if (function_exists('set_time_limit')) { |
| 964 | - @set_time_limit($execution_time); | |
| 1006 | + @set_time_limit($execution_time); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- runtime PHP setting with no WordPress API | |
| 965 | 1007 | } |
| 966 | 1008 | |
| 967 | 1009 | if ($lines === null) { |
| 968 | 1010 | $lines = $this->get_execution_setting('log_batch_size'); |
| @@ -967,9 +1009,9 @@ | ||
| 967 | 1009 | if ($lines === null) { |
| 968 | 1010 | $lines = $this->get_execution_setting('log_batch_size'); |
| 969 | 1011 | } |
| 970 | 1012 | |
| 971 | - $handle = fopen($file_path, 'r'); | |
| 1013 | + $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 | |
| 972 | 1014 | if (!$handle) { |
| 973 | 1015 | return false; |
| 974 | 1016 | } |
| 975 | 1017 | |
| @@ -998,9 +1040,9 @@ | ||
| 998 | 1040 | if (!empty($line) && $line_count < $lines) { |
| 999 | 1041 | array_unshift($result_lines, strrev($line)); |
| 1000 | 1042 | } |
| 1001 | 1043 | |
| 1002 | - fclose($handle); | |
| 1044 | + fclose($handle); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- operates on a native stream handle | |
| 1003 | 1045 | |
| 1004 | 1046 | return implode("\n", $result_lines); |
| 1005 | 1047 | } |
| 1006 | 1048 | } |