| @@ -10,8 +10,10 @@ | ||
| 10 | 10 | if (!defined('WPINC')) { |
| 11 | 11 | die; |
| 12 | 12 | } |
| 13 | 13 | |
| 14 | +require_once __DIR__ . '/privacy.php'; | |
| 15 | + | |
| 14 | 16 | /** |
| 15 | 17 | * WordPress Error Handler Class |
| 16 | 18 | */ |
| 17 | 19 | class MetaSync_WordPress_Error_Handler { |
| @@ -87,9 +89,9 @@ | ||
| 87 | 89 | * Setup WordPress error handlers - Only for plugin-specific errors |
| 88 | 90 | */ |
| 89 | 91 | private function setup_error_handlers() { |
| 90 | 92 | // Store previous handlers to chain them properly |
| 91 | - $this->previous_error_handler = set_error_handler(array($this, 'capture_php_error'), E_ALL); | |
| 93 | + $this->previous_error_handler = set_error_handler(array($this, 'capture_php_error'), E_ALL); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler -- plugin logger sink / last-resort fallback | |
| 92 | 94 | $this->previous_exception_handler = set_exception_handler(array($this, 'capture_exception')); |
| 93 | 95 | |
| 94 | 96 | // Hook into WordPress fatal error handler (only for plugin errors) |
| 95 | 97 | add_filter('wp_fatal_error_handler', array($this, 'capture_fatal_error')); |
| @@ -121,8 +123,14 @@ | ||
| 121 | 123 | if ($this->previous_error_handler && is_callable($this->previous_error_handler)) { |
| 122 | 124 | $handled = call_user_func($this->previous_error_handler, $severity, $message, $file, $line); |
| 123 | 125 | } |
| 124 | 126 | |
| 127 | + // Respect error_reporting() and the @ suppression operator — never capture masked errors | |
| 128 | + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.prevent_path_disclosure_error_reporting, PluginCheck.CodeAnalysis.PHPErrorReporting.DirectErrorReportingCall -- reads the current mask, does not print paths; reading the mask is the only way a custom handler can respect the @ operator | |
| 129 | + if (!(error_reporting() & $severity)) { | |
| 130 | + return $handled; | |
| 131 | + } | |
| 132 | + | |
| 125 | 133 | // Only capture fatal-level errors — skip warnings, notices, deprecated, strict |
| 126 | 134 | $fatal_severities = [E_USER_ERROR, E_RECOVERABLE_ERROR]; |
| 127 | 135 | if (!in_array($severity, $fatal_severities, true)) { |
| 128 | 136 | return $handled; |
| @@ -135,9 +143,9 @@ | ||
| 135 | 143 | 'severity' => $severity, |
| 136 | 144 | 'file' => $file, |
| 137 | 145 | 'line' => $line, |
| 138 | 146 | 'error_level' => $this->get_error_level($severity), |
| 139 | - 'backtrace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10) | |
| 147 | + 'backtrace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10) // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace -- genuine failure path, bounded, no secrets | |
| 140 | 148 | )); |
| 141 | 149 | } |
| 142 | 150 | |
| 143 | 151 | // Return the result from the previous handler, or false to continue normal error handling |
| @@ -148,10 +156,12 @@ | ||
| 148 | 156 | * Capture uncaught exceptions - Only plugin-specific exceptions |
| 149 | 157 | */ |
| 150 | 158 | public function capture_exception($exception) { |
| 151 | 159 | // First, call the previous exception handler if it exists |
| 160 | + $chained = false; | |
| 152 | 161 | if ($this->previous_exception_handler && is_callable($this->previous_exception_handler)) { |
| 153 | 162 | call_user_func($this->previous_exception_handler, $exception); |
| 163 | + $chained = true; | |
| 154 | 164 | } |
| 155 | 165 | |
| 156 | 166 | // Only capture exceptions from our plugin or directly related to our plugin |
| 157 | 167 | if ($this->should_capture_error($exception->getFile())) { |
| @@ -162,9 +172,16 @@ | ||
| 162 | 172 | 'line' => $exception->getLine(), |
| 163 | 173 | 'trace' => $exception->getTraceAsString(), |
| 164 | 174 | 'exception_object' => $exception |
| 165 | 175 | )); |
| 176 | + return; | |
| 166 | 177 | } |
| 178 | + | |
| 179 | + // Not our exception and nothing else handled it: restore PHP's default | |
| 180 | + // uncaught-exception handling instead of swallowing it silently. | |
| 181 | + if (!$chained) { | |
| 182 | + throw $exception; | |
| 183 | + } | |
| 167 | 184 | } |
| 168 | 185 | |
| 169 | 186 | /** |
| 170 | 187 | * Capture fatal errors |
| @@ -267,9 +284,9 @@ | ||
| 267 | 284 | /** |
| 268 | 285 | * Check if the current environment is localhost/development |
| 269 | 286 | */ |
| 270 | 287 | private function is_localhost() { |
| 271 | - $host = parse_url(home_url(), PHP_URL_HOST); | |
| 288 | + $host = wp_parse_url(home_url(), PHP_URL_HOST); | |
| 272 | 289 | |
| 273 | 290 | // Check for common localhost patterns |
| 274 | 291 | $localhost_patterns = [ |
| 275 | 292 | 'localhost', |
| @@ -282,9 +299,9 @@ | ||
| 282 | 299 | '.localhost' |
| 283 | 300 | ]; |
| 284 | 301 | |
| 285 | 302 | foreach ($localhost_patterns as $pattern) { |
| 286 | - if (strpos($host, $pattern) !== false) { | |
| 303 | + if ($host !== false && $host !== null && strpos($host, $pattern) !== false) { | |
| 287 | 304 | return true; |
| 288 | 305 | } |
| 289 | 306 | } |
| 290 | 307 | |
| @@ -325,9 +342,9 @@ | ||
| 325 | 342 | $context = array_merge($context, array( |
| 326 | 343 | 'wp_url' => home_url(), |
| 327 | 344 | 'plugin_version' => defined('METASYNC_VERSION') ? METASYNC_VERSION : '1.0.0', |
| 328 | 345 | 'plugin_name' => 'Search Engine Labs SEO (MetaSync)', |
| 329 | - 'error_timestamp' => date('Y-m-d H:i:s'), | |
| 346 | + 'error_timestamp' => gmdate('Y-m-d H:i:s'), | |
| 330 | 347 | 'site_info' => array( |
| 331 | 348 | 'site_title' => get_bloginfo('name'), |
| 332 | 349 | 'wp_version' => get_bloginfo('version'), |
| 333 | 350 | 'admin_email' => get_bloginfo('admin_email'), |
| @@ -336,12 +353,14 @@ | ||
| 336 | 353 | 'memory_limit' => ini_get('memory_limit'), |
| 337 | 354 | 'multisite' => is_multisite() |
| 338 | 355 | ), |
| 339 | 356 | 'request_info' => array( |
| 340 | - 'url' => $_SERVER['REQUEST_URI'] ?? 'unknown', | |
| 357 | + 'url' => metasync_telemetry_request_path(), | |
| 341 | 358 | 'method' => $_SERVER['REQUEST_METHOD'] ?? 'unknown', |
| 342 | 359 | 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown', |
| 343 | - 'referer' => $_SERVER['HTTP_REFERER'] ?? 'none' | |
| 360 | + 'referer' => isset($_SERVER['HTTP_REFERER']) | |
| 361 | + ? metasync_telemetry_request_path($_SERVER['HTTP_REFERER']) | |
| 362 | + : 'none' | |
| 344 | 363 | ) |
| 345 | 364 | )); |
| 346 | 365 | |
| 347 | 366 | // Determine severity level |
| @@ -501,8 +520,8 @@ | ||
| 501 | 520 | |
| 502 | 521 | // Initialize the error handler immediately when this file is loaded |
| 503 | 522 | // This ensures we catch errors that happen during plugin initialization |
| 504 | 523 | // Must be initialized early to catch parse errors and fatal errors |
| 505 | -if (class_exists('MetaSync_WordPress_Error_Handler')) { | |
| 524 | +if (!metasync_telemetry_is_disabled() && class_exists('MetaSync_WordPress_Error_Handler')) { | |
| 506 | 525 | new MetaSync_WordPress_Error_Handler(); |
| 507 | 526 | } |
| 508 | 527 | ?> |