You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
345 lines
11 KiB
345 lines
11 KiB
<?php
|
|
|
|
namespace CodeHydrater\bootstrap;
|
|
|
|
define('WORD_WRAP_CHRS', 80); // Letters before Line Wrap on Errors
|
|
|
|
// Configuration
|
|
if (! defined('BaseDir')) {
|
|
define('BaseDir', ""); // To avoid defination errors
|
|
define('LOG_FILE', false);
|
|
} else {
|
|
define('LOG_FILE', BaseDir . '/protected/logs/errors.log.txt');
|
|
}
|
|
|
|
function fallback_dev(): bool {
|
|
if (! defined("ENVIRONMENT")) {
|
|
return false; // Force (false) which means Live on unknown state
|
|
}
|
|
return (ENVIRONMENT === 'development') ? true : false;
|
|
}
|
|
|
|
function fallback_get_config(string $key, string $item, $default_when_not_found = false) {
|
|
if (! method_exists("\CodeHydrater\bootstrap\configure", "has")) {
|
|
return $default_when_not_found;
|
|
}
|
|
$value = $default_when_not_found; // Init defaults
|
|
if (configure::has($key, $item)) {
|
|
$value = configure::get($key, $item);
|
|
if ($value === null) {
|
|
return $default_when_not_found;
|
|
}
|
|
}
|
|
return $value;
|
|
}
|
|
|
|
function should_be_broken(string $msg = ""): bool {
|
|
|
|
$clear = fallback_get_config("CodeHydrater", "clear_screen_on_prod_errors");
|
|
|
|
if (fallback_get_config("CodeHydrater", "if_live_with_error_show_broken_page") === true) {
|
|
broken_error($msg, $clear);
|
|
return true;
|
|
} else {
|
|
broken_error($msg, false); // Don't Clear
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function fallback_mini_view_prod(string $msg, string $file = "prod_error.php") {
|
|
if (fallback_get_config("CodeHydrater", "clear_screen_on_prod_errors") === false) {
|
|
return $msg; // Return Error Message on False and do not clear the Screen
|
|
}
|
|
echo fallback_mini_view($msg, $file);
|
|
}
|
|
|
|
function fallback_mini_view_dev(string $msg, string $file = "dev_error.php") {
|
|
if (fallback_get_config("CodeHydrater", "clear_screen_on_dev_errors") === false) {
|
|
return $msg; // Return Error Message on False and do not clear the Screen
|
|
}
|
|
echo fallback_mini_view($msg, $file);
|
|
}
|
|
|
|
function fallback_mini_view(string $msg, string $file = "dev_error.php"): string {
|
|
|
|
@ob_end_clean(); // Wipe all HTML content before this....
|
|
if (!headers_sent()) {
|
|
header('Content-Type: text/html; charset=utf-8', true, 200);
|
|
}
|
|
|
|
views::ob_start();
|
|
|
|
$saved_ob_level = ob_get_level();
|
|
|
|
$mini = new \stdClass();
|
|
$mini->page_output = $msg;
|
|
if (fallback_requires($file, false, $mini) === false) {
|
|
fallback_requires("views/on_error/" . $file, true, $mini);
|
|
}
|
|
|
|
// If you really must close all of your output buffers except one, this'll do it:
|
|
while (ob_get_level() > $saved_ob_level) {
|
|
ob_end_flush();
|
|
}
|
|
|
|
return @ob_get_clean();
|
|
}
|
|
|
|
function fallback_logger(string $logMessage, string $file = LOG_FILE) {
|
|
if (method_exists("CodeHydrater\services\log", "write")) {
|
|
$logger = new \CodeHydrater\services\log("errors");
|
|
$written = $logger->write($logMessage);
|
|
if ($written === false) {
|
|
// Open a connection to the system logger
|
|
openlog("PHP App", LOG_PID | LOG_ODELAY, LOG_LOCAL0);
|
|
|
|
// Write a message
|
|
syslog(LOG_INFO, $logMessage);
|
|
|
|
// Close the connection
|
|
closelog();
|
|
}
|
|
} else {
|
|
$Message = date('[Y-m-d H:i:s]') . $logMessage;
|
|
file_put_contents($file, $Message, FILE_APPEND);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Format message with appropriate colors based on environment
|
|
*/
|
|
function formatMessage($message, $type = 'error') {
|
|
if (PHP_SAPI === 'cli') {
|
|
// CLI color formatting
|
|
$colors = [
|
|
'error' => "\033[31m", // red
|
|
'warning' => "\033[33m", // yellow
|
|
'notice' => "\033[36m", // cyan
|
|
'reset' => "\033[0m" // reset
|
|
];
|
|
$color = $colors[$type] ?? $colors['error'];
|
|
$message = wordwrap($message, WORD_WRAP_CHRS, "\n");
|
|
return $color . $message . $colors['reset'] . PHP_EOL;
|
|
} else {
|
|
// Web HTML formatting
|
|
$styles = [
|
|
'error' => 'uk-alert-danger',
|
|
'warning' => 'uk-alert-warning',
|
|
'notice' => 'uk-alert-primary'
|
|
];
|
|
$style = $styles[$type] ?? $styles['error'];
|
|
$message = wordwrap($message, WORD_WRAP_CHRS, "<br>\n");
|
|
|
|
$assets = "/assets/uikit/css/uikit.gradient.min.css";
|
|
if (file_exists(BaseDir. "/public/" . $assets)) {
|
|
$msg = '<link rel="stylesheet" href="' . $assets . '" type="text/css" media="all" />';
|
|
$msg .= "<div class=\"uk-alert $style\">";
|
|
} else {
|
|
$msg = "<div style=\"color: red;padding:10px;border:1px solid #f99;margin:10px;\"";
|
|
}
|
|
$msg .= $message;
|
|
$msg .= "</div>";
|
|
return $msg;
|
|
}
|
|
}
|
|
|
|
// Custom error handler
|
|
set_error_handler(function($errno, $errstr, $errfile, $errline) {
|
|
// Skip if error reporting is turned off
|
|
if (!(error_reporting() & $errno)) {
|
|
return false;
|
|
}
|
|
if (method_exists("errors", "get_handle_shutdown_errors") && ! errors::get_handle_shutdown_errors()) {
|
|
return false;
|
|
}
|
|
|
|
$errorTypes = [
|
|
E_ERROR => ['ERROR', 'error'],
|
|
E_WARNING => ['WARNING', 'warning'],
|
|
E_PARSE => ['PARSE ERROR', 'error'],
|
|
E_NOTICE => ['NOTICE', 'notice'],
|
|
E_CORE_ERROR => ['CORE ERROR', 'error'],
|
|
E_CORE_WARNING => ['CORE WARNING', 'warning'],
|
|
E_COMPILE_ERROR => ['COMPILE ERROR', 'error'],
|
|
E_COMPILE_WARNING => ['COMPILE WARNING', 'warning'],
|
|
E_USER_ERROR => ['USER ERROR', 'error'],
|
|
E_USER_WARNING => ['USER WARNING', 'warning'],
|
|
E_USER_NOTICE => ['USER NOTICE', 'notice'],
|
|
E_STRICT => ['STRICT', 'notice'],
|
|
E_RECOVERABLE_ERROR=> ['RECOVERABLE ERROR', 'error'],
|
|
E_DEPRECATED => ['DEPRECATED', 'warning'],
|
|
E_USER_DEPRECATED => ['USER DEPRECATED', 'warning']
|
|
];
|
|
|
|
$errorInfo = $errorTypes[$errno] ?? ['UNKNOWN', 'error'];
|
|
$errorType = $errorInfo[0];
|
|
$errorCategory = $errorInfo[1];
|
|
|
|
$logMessage = " [$errorType] $errstr in $errfile on line $errline" . PHP_EOL;
|
|
$displayMessage = "$errorType: $errstr in $errfile on line $errline";
|
|
|
|
fallback_logger($logMessage);
|
|
|
|
// Display in development environment
|
|
if (fallback_dev()) {
|
|
echo fallback_mini_view_dev(formatMessage($displayMessage, $errorCategory));
|
|
// Prevent PHP's default error handler
|
|
return true;
|
|
}
|
|
should_be_broken($displayMessage);
|
|
return true;
|
|
});
|
|
|
|
// Handle exceptions
|
|
set_exception_handler(function($e) {
|
|
if (method_exists("errors", "get_handle_exceptions") && ! errors::get_handle_exceptions()) {
|
|
return false;
|
|
}
|
|
$logMessage = " [EXCEPTION] " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine() . PHP_EOL;
|
|
$displayMessage = "UNCAUGHT EXCEPTION: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine();
|
|
|
|
fallback_logger($logMessage);
|
|
|
|
// Are we developing...? if so, show details on error
|
|
if (fallback_dev()) {
|
|
echo fallback_mini_view_dev(formatMessage($displayMessage, 'error'));
|
|
} else {
|
|
if (should_be_broken($displayMessage) === false) {
|
|
// In production, show user-friendly message
|
|
echo PHP_SAPI === 'cli'
|
|
? "An error occurred. Our team has been notified." . PHP_EOL
|
|
: "An error occurred. Our team has been notified.";
|
|
}
|
|
}
|
|
});
|
|
|
|
// Handle fatal errors
|
|
register_shutdown_function(function() {
|
|
if (method_exists("errors", "get_handle_global_errors") && ! errors::get_handle_global_errors()) {
|
|
return false;
|
|
}
|
|
$error = error_get_last();
|
|
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
|
|
$logMessage = " [FATAL] {$error['message']} in {$error['file']} on line {$error['line']}" . PHP_EOL;
|
|
$displayMessage = "FATAL ERROR: {$error['message']} in {$error['file']} on line {$error['line']}";
|
|
|
|
fallback_logger($logMessage);
|
|
|
|
// If developing, show error
|
|
if (fallback_dev()) {
|
|
echo fallback_mini_view_dev(formatMessage($displayMessage, 'error'));
|
|
} else {
|
|
should_be_broken($displayMessage);
|
|
}
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Displays Error page
|
|
* @param type $ex error message
|
|
*/
|
|
function broken_error(string $ex = "", bool $clear = false): void {
|
|
if (fallback_is_cli()) {
|
|
echo $ex;
|
|
return;
|
|
}
|
|
$use_api = fallback_is_api();
|
|
|
|
if ($use_api === true) {
|
|
$internal_error = "An unexpected error occured.";
|
|
$status_code = 500;
|
|
fallback_json_error($internal_error, $status_code);
|
|
return;
|
|
}
|
|
|
|
$error_message = "<h1>Sorry, we had an error...</h1><p>We apologize for any inconvenience this may cause.</p>";
|
|
if ($clear === false) {
|
|
echo $error_message;
|
|
} else {
|
|
echo fallback_mini_view_prod($error_message);
|
|
}
|
|
}
|
|
|
|
function fallback_json_error(string $data, int $status_code): void {
|
|
if (!headers_sent()) {
|
|
header($_SERVER['SERVER_PROTOCOL'] . " " . $status_code);
|
|
/*
|
|
* Allow JavaScript from anywhere. CORS - Cross Origin Resource Sharing
|
|
* @link https://manning-content.s3.amazonaws.com/download/f/54fa960-332e-4a8c-8e7f-1eb213831e5a/CORS_ch01.pdf
|
|
*/
|
|
header("Access-Control-Allow-Origin: *");
|
|
header("Access-Control-Allow-Methods: *");
|
|
header('Content-Type: application/json; charset=utf-8', true, intval($status_code));
|
|
}
|
|
echo json_encode($data);
|
|
}
|
|
|
|
function fallback_is_api(): bool {
|
|
$uri = fallback_get_uri();
|
|
if (preg_match('~/api(\d*)/~', $uri)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function fallback_is_cli() {
|
|
if (defined('STDIN')) {
|
|
return true;
|
|
}
|
|
|
|
if (php_sapi_name() === 'cli') {
|
|
return true;
|
|
}
|
|
|
|
if (array_key_exists('SHELL', $_ENV)) {
|
|
return true;
|
|
}
|
|
|
|
if (!isset($_SERVER['REMOTE_ADDR']) && !isset($_SERVER['HTTP_USER_AGENT'])) {
|
|
return true;
|
|
}
|
|
|
|
if (!array_key_exists('REQUEST_METHOD', $_SERVER)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function fallback_get_uri(): ?string {
|
|
$uri = strtok($_SERVER['REQUEST_URI'], "?");
|
|
if ($uri === false) {
|
|
return "";
|
|
}
|
|
return $uri;
|
|
}
|
|
|
|
function fallback_requires(string $file, bool $fw = false, $local = null): bool {
|
|
$exists = method_exists("\CodeHydrater\bootstrap\requires", "secure_include");
|
|
if ($exists) {
|
|
if ($fw === false) {
|
|
$path = UseDir::ONERROR;
|
|
} else {
|
|
$path = UseDir::FRAMEWORK;
|
|
}
|
|
$good = \CodeHydrater\bootstrap\requires::secure_include($file, $path);
|
|
return ($good === false) ? false : true; // handle contents vs. failure = false
|
|
} else {
|
|
if ($fw === false) {
|
|
$view = CodeHydrater_PROJECT . "views/on_error/" . $file;
|
|
} else {
|
|
$view = CodeHydrater_FRAMEWORK . $file;
|
|
}
|
|
if (file_exists($view)) {
|
|
include $view;
|
|
return true;
|
|
}
|
|
}
|
|
return false; // You have failed me for the last time! LOL
|
|
}
|
|
|
|
// Test the error handler (uncomment to test)
|
|
// trigger_error("This is a test warning", E_USER_WARNING);
|
|
// throw new Exception("This is a test exception");
|
|
// nonexistentFunction(); // Will trigger a fatal error in shutdown handler
|