-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
/
ExceptionHandler.php
202 lines (181 loc) · 5.05 KB
/
ExceptionHandler.php
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\Exception;
\defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Error\AbstractRenderer;
use Joomla\CMS\Factory;
use Joomla\CMS\Log\Log;
/**
* Displays the custom error page when an uncaught exception occurs.
*
* @since 3.0
*/
class ExceptionHandler
{
/**
* Handles an error triggered with the E_USER_DEPRECATED level.
*
* @param integer $errorNumber The level of the raised error, represented by the E_* constants.
* @param string $errorMessage The error message.
* @param string $errorFile The file the error was triggered from.
* @param integer $errorLine The line number the error was triggered from.
*
* @return boolean
*
* @since 4.0.0
*/
public static function handleUserDeprecatedErrors(int $errorNumber, string $errorMessage, string $errorFile, int $errorLine): bool
{
// We only want to handle user deprecation messages, these will be triggered in code
if ($errorNumber === E_USER_DEPRECATED)
{
Log::add(
$errorMessage,
Log::WARNING,
'deprecated'
);
// If debug mode is enabled, we want to let PHP continue to handle the error; otherwise, we can bail early
if (\defined('JDEBUG') && JDEBUG)
{
return true;
}
}
// Always return false, this will tell PHP to handle the error internally
return false;
}
/**
* Render the error page based on an exception.
*
* @param \Throwable $error An Exception or Throwable (PHP 7+) object for which to render the error page.
*
* @return void
*
* @since 3.0
*/
public static function render(\Throwable $error)
{
try
{
// Try to log the error, but don't let the logging cause a fatal error
try
{
Log::add(
sprintf(
'Uncaught Throwable of type %1$s thrown with message "%2$s". Stack trace: %3$s',
\get_class($error),
$error->getMessage(),
$error->getTraceAsString()
),
Log::CRITICAL,
'error'
);
}
catch (\Throwable $e)
{
// Logging failed, don't make a stink about it though
}
$app = Factory::getApplication();
// Flag if we are on cli
$isCli = $app->isClient('cli');
// If site is offline and it's a 404 error, just go to index (to see offline message, instead of 404)
if (!$isCli && $error->getCode() == '404' && $app->get('offline') == 1)
{
$app->redirect('index.php');
}
/*
* Try and determine the format to render the error page in
*
* First we check if a Document instance was registered to Factory and use the type from that if available
* If a type doesn't exist for that format, we try to use the format from the application's Input object
* Lastly, if all else fails, we default onto the HTML format to at least render something
*/
if (Factory::$document)
{
$format = Factory::$document->getType();
}
else
{
$format = $app->input->getString('format', 'html');
}
try
{
$renderer = AbstractRenderer::getRenderer($format);
}
catch (\InvalidArgumentException $e)
{
// Default to the HTML renderer
$renderer = AbstractRenderer::getRenderer('html');
}
// Reset the document object in the factory, this gives us a clean slate and lets everything render properly
Factory::$document = $renderer->getDocument();
Factory::getApplication()->loadDocument(Factory::$document);
$data = $renderer->render($error);
// If nothing was rendered, just use the message from the Exception
if (empty($data))
{
$data = $error->getMessage();
}
if ($isCli)
{
echo $data;
}
else
{
/** @var CMSApplication $app */
// Do not allow cache
$app->allowCache(false);
$app->setBody($data);
}
// This return is needed to ensure the test suite does not trigger the non-Exception handling below
return;
}
catch (\Throwable $errorRendererError)
{
// Pass the error down
}
/*
* To reach this point in the code means there was an error creating the error page.
*
* Let global handler to handle the error, @see bootstrap.php
*/
if (isset($errorRendererError))
{
/*
* Here the thing, at this point we have 2 exceptions:
* $errorRendererError - the error caused by error renderer
* $error - the main error
*
* We need to show both exceptions, without loss of trace information, so use a bit of magic to merge them.
*
* Use exception nesting feature: rethrow the exceptions, an exception thrown in a finally block
* will take unhandled exception as previous.
* So PHP will add $error Exception as previous to $errorRendererError Exception to keep full error stack.
*/
try
{
try
{
throw $error;
}
finally
{
throw $errorRendererError;
}
}
catch (\Throwable $finalError)
{
throw $finalError;
}
}
else
{
throw $error;
}
}
}