<?php /***************************************************************************** * ErrorHandler.php * Contains key class ErrorHandler. * * Kabuki CMS (C) 2013-2016, Aaron van Geffen *****************************************************************************/ class ErrorHandler { private static $error_count = 0; private static $handling_error; public static function enable() { set_error_handler('ErrorHandler::handleError'); ini_set("display_errors", DEBUG ? "On" : "Off"); } public static function disable() { set_error_handler(NULL); ini_set("display_errors", "Off"); } // Handler for standard PHP error messages. public static function handleError($error_level, $error_message, $file, $line, $context = null) { // Don't handle suppressed errors (e.g. through @ operator) if (!(error_reporting() & $error_level)) return; // Prevent recursing if we've messed up in this code path. if (self::$handling_error) return; self::$error_count++; self::$handling_error = true; // Basically, htmlspecialchars it, minus '&' for HTML entities. $error_message = strtr($error_message, ['<' => '<', '>' => '>', '"' => '"', "\t" => ' ']); $error_message = strtr($error_message, ['<br>' => "<br>", '<br />' => "<br>", '<b>' => '<strong>', '</b>' => '</strong>', '<pre>' => '<pre>', '</pre>' => '</pre>']); // Generate a bunch of useful information to ease debugging later. $debug_info = self::getDebugInfo(debug_backtrace()); // Log the error in the database. self::logError($error_message, $debug_info, $file, $line); // Are we considering this fatal? Then display and exit. // !!! TODO: should we consider warnings fatal? if (true) // DEBUG || (!DEBUG && $error_level === E_WARNING || $error_level === E_USER_WARNING)) self::display($file . ' (' . $line . ')<br>' . $error_message, $debug_info); // If it wasn't a fatal error, well... self::$handling_error = false; } public static function getDebugInfo(array $trace) { $debug_info = "Backtrace:\n"; $debug_info .= self::formatBacktrace($trace); // Include info on the contents of superglobals. if (!empty($_SESSION)) $debug_info .= "\nSESSION: " . var_export($_SESSION, true); if (!empty($_POST)) $debug_info .= "\nPOST: " . var_export($_POST, true); if (!empty($_GET)) $debug_info .= "\nGET: " . var_export($_GET, true); return $debug_info; } private static function formatBacktrace(array $trace) { $buffer = ''; $skipping = true; foreach ($trace as $i => $call) { if (isset($call['class']) && ($call['class'] === 'ErrorHandler' || $call['class'] === 'Database') || isset($call['function']) && $call['function'] === 'preg_replace_callback') { if (!$skipping) { $buffer .= "[...]\n"; $skipping = true; } continue; } else $skipping = false; $file = isset($call['file']) ? str_replace(BASEDIR, '', $call['file']) : 'Unknown'; $object = isset($call['class']) ? $call['class'] . $call['type'] : ''; $args = []; if (isset($call['args'])) { foreach ($call['args'] as $j => $arg) { // Only include the class name for objects if (is_object($arg)) $args[$j] = get_class($arg) . '{}'; // Export everything else -- including arrays else $args[$j] = var_export($arg, true); } } $buffer .= '#' . str_pad($i, 3, ' ') . $object . $call['function'] . '(' . implode(', ', $args) . ')' . ' called at [' . $file . ':' . $call['line'] . "]\n"; } return $buffer; } // Logs an error into the database. private static function logError($error_message = '', $debug_info = '', $file = '', $line = 0) { if (!ErrorLog::log([ 'message' => $error_message, 'debug_info' => $debug_info, 'file' => str_replace(BASEDIR, '', $file), 'line' => $line, 'id_user' => Registry::has('user') ? Registry::get('user')->getUserId() : 0, 'ip_address' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '', 'request_uri' => isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '', ])) { header('HTTP/1.1 503 Service Temporarily Unavailable'); echo '<h2>An Error Occurred</h2><p>Our software could not connect to the database. We apologise for any inconvenience and ask you to check back later.</p>'; exit; } return $error_message; } public static function display($message, $debug_info, $is_sensitive = true) { $is_admin = Registry::has('user') && Registry::get('user')->isAdmin(); // Just show the message if we're running in a console. if (empty($_SERVER['HTTP_HOST'])) { echo $message; exit; } // JSON request? elseif (isset($_GET['json']) || isset($_GET['format']) && $_GET['format'] == 'json') { if (DEBUG || $is_admin) echo json_encode(['error' => $message . "\n\n" . $debug_info]); elseif (!$is_sensitive) echo json_encode(['error' => $message]); else echo json_encode(['error' => 'Our apologies, an error occurred while we were processing your request. Please try again later, or contact us if the problem persists.']); exit; } // Initialise the main template to present a nice message to the user. $page = new MainTemplate('An error occurred!'); // Show the error. $is_admin = Registry::has('user') && Registry::get('user')->isAdmin(); if (DEBUG || $is_admin) { $page->adopt(new DummyBox('An error occurred!', '<p>' . $message . '</p><pre>' . $debug_info . '</pre>')); // Let's provide the admin navigation despite it all! if ($is_admin) { $page->appendStylesheet(BASEURL . '/css/admin.css'); } } elseif (!$is_sensitive) $page->adopt(new DummyBox('An error occurred!', '<p>' . $message . '</p>')); else $page->adopt(new DummyBox('An error occurred!', '<p>Our apologies, an error occurred while we were processing your request. Please try again later, or contact us if the problem persists.</p>')); // If we got this far, make sure we're not showing stuff twice. ob_end_clean(); // Render the page. $page->html_main(); exit; } }