summaryrefslogtreecommitdiffstats
path: root/inc/errorhandler.inc.php
blob: ce969966fedf5270c44e1604c3d55f22680fb11f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
<?php

declare(strict_types=1);

use JetBrains\PhpStorm\NoReturn;

class ErrorHandler
{


	/**
	 * Displays an error message and stops script execution.
	 * If CONFIG_DEBUG is true, it will also dump a stack trace
	 * and all globally defined variables.
	 * (As this might reveal sensitive data you should never enable it in production)
	 */
	#[NoReturn]
	public static function traceError(string $message): void
	{
		if ((defined('API') && API) || (defined('AJAX') && AJAX) || php_sapi_name() === 'cli') {
			error_log('API ERROR: ' . $message);
			error_log(self::formatBacktracePlain(debug_backtrace()));
		}
		if (php_sapi_name() === 'cli') {
			// Don't spam HTML when invoked via cli, above error_log should have gone to stdout/stderr
			exit(1);
		}
		Header('HTTP/1.1 500 Internal Server Error');
		if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'html') === false) {
			Header('Content-Type: text/plain; charset=utf-8');
			echo 'API ERROR: ', $message, "\n", self::formatBacktracePlain(debug_backtrace());
			exit(0);
		}
		Header('Content-Type: text/html; charset=utf-8');
		echo '<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><style>', "\n",
		".arg { color: red; background: white; }\n",
		"h1 a { color: inherit; text-decoration: inherit; font-weight: inherit; }\n",
		'</style><title>Fatal Error</title></head><body>';
		echo '<h1>Flagrant <a href="https://www.youtube.com/watch?v=7rrZ-sA4FQc&t=2m2s" target="_blank">S</a>ystem error</h1>';
		echo "<h2>Message</h2><pre>$message</pre>";
		if (strpos($message, 'Database') !== false) {
			echo '<div><a href="install.php">Try running database setup</a></div>';
		}
		echo "<br><br>";
		if (defined('CONFIG_DEBUG') && CONFIG_DEBUG) {
			global $SLX_ERRORS;
			if (!empty($SLX_ERRORS)) {
				echo '<h2>PHP Errors</h2><pre>';
				foreach ($SLX_ERRORS as $error) {
					echo htmlspecialchars("{$error['errstr']} ({$error['errfile']}:{$error['errline']}\n");
				}
				echo '</pre>';
			}
			echo "<h2>Stack Trace</h2>";
			echo '<pre>', self::formatBacktraceHtml(debug_backtrace()), '</pre>';
			echo "<h2>Globals</h2><pre>";
			echo htmlspecialchars(print_r($GLOBALS, true));
			echo '</pre>';
		} else {
			echo <<<SADFACE
<pre>
________________________¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶________
____________________¶¶¶___________________¶¶¶¶_____
________________¶¶¶_________________________¶¶¶¶___
______________¶¶______________________________¶¶¶__
___________¶¶¶_________________________________¶¶¶_
_________¶¶_____________________________________¶¶¶
________¶¶_________¶¶¶¶¶___________¶¶¶¶¶_________¶¶
______¶¶__________¶¶¶¶¶¶__________¶¶¶¶¶¶_________¶¶
_____¶¶___________¶¶¶¶____________¶¶¶¶___________¶¶
____¶¶___________________________________________¶¶
___¶¶___________________________________________¶¶_
__¶¶____________________¶¶¶¶____________________¶¶_
_¶¶_______________¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶______________¶¶__
_¶¶____________¶¶¶¶___________¶¶¶¶¶___________¶¶___
¶¶¶_________¶¶¶__________________¶¶__________¶¶____
¶¶_________¶______________________¶¶________¶¶_____
¶¶¶______¶________________________¶¶_______¶¶______
¶¶¶_____¶_________________________¶¶_____¶¶________
_¶¶¶___________________________________¶¶__________
__¶¶¶________________________________¶¶____________
___¶¶¶____________________________¶¶_______________
____¶¶¶¶______________________¶¶¶__________________
_______¶¶¶¶¶_____________¶¶¶¶¶_____________________
</pre>
SADFACE;
		}
		echo '</body></html>';
		exit(0);
	}

	public static function formatBacktraceHtml(array $trace): string
	{
		$output = '';
		foreach ($trace as $idx => $line) {
			$args = array();
			foreach ($line['args'] as $arg) {
				$arg = self::formatArgument($arg);
				$args[] = '<span class="arg">' . htmlspecialchars($arg) . '</span>';
			}
			$frame = str_pad('#' . $idx, 3, ' ', STR_PAD_LEFT);
			$function = htmlspecialchars($line['function']);
			$args = implode(', ', $args);
			$file = preg_replace('~(/[^/]+)$~', '<b>$1</b>', htmlspecialchars($line['file']));
			// Add line
			$output .= $frame . ' ' . $function . '<b>(</b>'
				. $args . '<b>)</b>' . ' @ <i>' . $file . '</i>:' . $line['line'] . "\n";
		}
		return $output;
	}

	public static function formatBacktracePlain(array $trace): string
	{
		$output = '';
		foreach ($trace as $idx => $line) {
			$args = array();
			foreach ($line['args'] as $arg) {
				$args[] = self::formatArgument($arg);
			}
			$frame = str_pad('#' . $idx, 3, ' ', STR_PAD_LEFT);
			$args = implode(', ', $args);
			// Add line
			$output .= "\n" . $frame . ' ' . $line['function'] . '('
				. $args . ')' . ' @ ' . $line['file'] . ':' . $line['line'];
		}
		return $output;
	}

	private static function formatArgument($arg, bool $expandArray = true): string
	{
		if (is_string($arg)) {
			$arg = "'$arg'";
		} elseif (is_object($arg)) {
			$arg = 'instanceof ' . get_class($arg);
		} elseif (is_array($arg)) {
			if ($expandArray && count($arg) < 20) {
				$expanded = '';
				foreach ($arg as $key => $value) {
					if (!empty($expanded)) {
						$expanded .= ', ';
					}
					$expanded .= $key . ': ' . self::formatArgument($value, false);
					if (strlen($expanded) > 200)
						break;
				}
				if (strlen($expanded) <= 200)
					return '[' . $expanded . ']';
			}
			$arg = 'Array(' . count($arg) . ')';
		}
		return (string)$arg;
	}
}