Aaron van Geffen
2a528f2830
`var_dump` was the wrong function to call for objects, as it would just output all object data to the output buffer... Let's generalise and use `var_export` instead :-)
191 lines
6.0 KiB
PHP
191 lines
6.0 KiB
PHP
<?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: " . print_r($_SESSION, true);
|
|
if (!empty($_POST))
|
|
$debug_info .= "\nPOST: " . print_r($_POST, true);
|
|
if (!empty($_GET))
|
|
$debug_info .= "\nGET: " . print_r($_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 Occured</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 occured 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 occured!');
|
|
|
|
// Show the error.
|
|
$is_admin = Registry::has('user') && Registry::get('user')->isAdmin();
|
|
if (DEBUG || $is_admin)
|
|
{
|
|
$page->adopt(new DummyBox('An error occured!', '<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 occured!', '<p>' . $message . '</p>'));
|
|
else
|
|
$page->adopt(new DummyBox('An error occured!', '<p>Our apologies, an error occured 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;
|
|
}
|
|
}
|