Mercurial > hg > cmmr2012-drupal-site
comparison core/includes/errors.inc @ 0:c75dbcec494b
Initial commit from drush-created site
author | Chris Cannam |
---|---|
date | Thu, 05 Jul 2018 14:24:15 +0000 |
parents | |
children | a9cd425dd02b |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:c75dbcec494b |
---|---|
1 <?php | |
2 | |
3 /** | |
4 * @file | |
5 * Functions for error handling. | |
6 */ | |
7 | |
8 use Drupal\Component\Utility\SafeMarkup; | |
9 use Drupal\Component\Utility\Xss; | |
10 use Drupal\Core\Logger\RfcLogLevel; | |
11 use Drupal\Core\Render\Markup; | |
12 use Drupal\Core\Utility\Error; | |
13 use Symfony\Component\HttpFoundation\Response; | |
14 | |
15 /** | |
16 * Maps PHP error constants to watchdog severity levels. | |
17 * | |
18 * The error constants are documented at | |
19 * http://php.net/manual/errorfunc.constants.php | |
20 * | |
21 * @ingroup logging_severity_levels | |
22 */ | |
23 function drupal_error_levels() { | |
24 $types = [ | |
25 E_ERROR => ['Error', RfcLogLevel::ERROR], | |
26 E_WARNING => ['Warning', RfcLogLevel::WARNING], | |
27 E_PARSE => ['Parse error', RfcLogLevel::ERROR], | |
28 E_NOTICE => ['Notice', RfcLogLevel::NOTICE], | |
29 E_CORE_ERROR => ['Core error', RfcLogLevel::ERROR], | |
30 E_CORE_WARNING => ['Core warning', RfcLogLevel::WARNING], | |
31 E_COMPILE_ERROR => ['Compile error', RfcLogLevel::ERROR], | |
32 E_COMPILE_WARNING => ['Compile warning', RfcLogLevel::WARNING], | |
33 E_USER_ERROR => ['User error', RfcLogLevel::ERROR], | |
34 E_USER_WARNING => ['User warning', RfcLogLevel::WARNING], | |
35 E_USER_NOTICE => ['User notice', RfcLogLevel::NOTICE], | |
36 E_STRICT => ['Strict warning', RfcLogLevel::DEBUG], | |
37 E_RECOVERABLE_ERROR => ['Recoverable fatal error', RfcLogLevel::ERROR], | |
38 E_DEPRECATED => ['Deprecated function', RfcLogLevel::DEBUG], | |
39 E_USER_DEPRECATED => ['User deprecated function', RfcLogLevel::DEBUG], | |
40 ]; | |
41 | |
42 return $types; | |
43 } | |
44 | |
45 /** | |
46 * Provides custom PHP error handling. | |
47 * | |
48 * @param $error_level | |
49 * The level of the error raised. | |
50 * @param $message | |
51 * The error message. | |
52 * @param $filename | |
53 * The filename that the error was raised in. | |
54 * @param $line | |
55 * The line number the error was raised at. | |
56 * @param $context | |
57 * An array that points to the active symbol table at the point the error | |
58 * occurred. | |
59 */ | |
60 function _drupal_error_handler_real($error_level, $message, $filename, $line, $context) { | |
61 if ($error_level & error_reporting()) { | |
62 $types = drupal_error_levels(); | |
63 list($severity_msg, $severity_level) = $types[$error_level]; | |
64 $backtrace = debug_backtrace(); | |
65 $caller = Error::getLastCaller($backtrace); | |
66 | |
67 // We treat recoverable errors as fatal. | |
68 $recoverable = $error_level == E_RECOVERABLE_ERROR; | |
69 // As __toString() methods must not throw exceptions (recoverable errors) | |
70 // in PHP, we allow them to trigger a fatal error by emitting a user error | |
71 // using trigger_error(). | |
72 $to_string = $error_level == E_USER_ERROR && substr($caller['function'], -strlen('__toString()')) == '__toString()'; | |
73 _drupal_log_error([ | |
74 '%type' => isset($types[$error_level]) ? $severity_msg : 'Unknown error', | |
75 // The standard PHP error handler considers that the error messages | |
76 // are HTML. We mimick this behavior here. | |
77 '@message' => Markup::create(Xss::filterAdmin($message)), | |
78 '%function' => $caller['function'], | |
79 '%file' => $caller['file'], | |
80 '%line' => $caller['line'], | |
81 'severity_level' => $severity_level, | |
82 'backtrace' => $backtrace, | |
83 '@backtrace_string' => (new \Exception())->getTraceAsString(), | |
84 ], $recoverable || $to_string); | |
85 } | |
86 // If the site is a test site then fail for user deprecations so they can be | |
87 // caught by the deprecation error handler. | |
88 elseif (DRUPAL_TEST_IN_CHILD_SITE && $error_level === E_USER_DEPRECATED) { | |
89 $backtrace = debug_backtrace(); | |
90 $caller = Error::getLastCaller($backtrace); | |
91 _drupal_error_header( | |
92 Markup::create(Xss::filterAdmin($message)), | |
93 'User deprecated function', | |
94 $caller['function'], | |
95 $caller['file'], | |
96 $caller['line'] | |
97 ); | |
98 } | |
99 } | |
100 | |
101 /** | |
102 * Determines whether an error should be displayed. | |
103 * | |
104 * When in maintenance mode or when error_level is ERROR_REPORTING_DISPLAY_ALL, | |
105 * all errors should be displayed. For ERROR_REPORTING_DISPLAY_SOME, $error | |
106 * will be examined to determine if it should be displayed. | |
107 * | |
108 * @param $error | |
109 * Optional error to examine for ERROR_REPORTING_DISPLAY_SOME. | |
110 * | |
111 * @return | |
112 * TRUE if an error should be displayed. | |
113 */ | |
114 function error_displayable($error = NULL) { | |
115 if (defined('MAINTENANCE_MODE')) { | |
116 return TRUE; | |
117 } | |
118 $error_level = _drupal_get_error_level(); | |
119 if ($error_level == ERROR_REPORTING_DISPLAY_ALL || $error_level == ERROR_REPORTING_DISPLAY_VERBOSE) { | |
120 return TRUE; | |
121 } | |
122 if ($error_level == ERROR_REPORTING_DISPLAY_SOME && isset($error)) { | |
123 return $error['%type'] != 'Notice' && $error['%type'] != 'Strict warning'; | |
124 } | |
125 return FALSE; | |
126 } | |
127 | |
128 /** | |
129 * Logs a PHP error or exception and displays an error page in fatal cases. | |
130 * | |
131 * @param $error | |
132 * An array with the following keys: %type, @message, %function, %file, | |
133 * %line, @backtrace_string, severity_level, and backtrace. All the parameters | |
134 * are plain-text, with the exception of @message, which needs to be an HTML | |
135 * string, and backtrace, which is a standard PHP backtrace. | |
136 * @param bool $fatal | |
137 * TRUE for: | |
138 * - An exception is thrown and not caught by something else. | |
139 * - A recoverable fatal error, which is a fatal error. | |
140 * Non-recoverable fatal errors cannot be logged by Drupal. | |
141 */ | |
142 function _drupal_log_error($error, $fatal = FALSE) { | |
143 $is_installer = drupal_installation_attempted(); | |
144 | |
145 // Backtrace array is not a valid replacement value for t(). | |
146 $backtrace = $error['backtrace']; | |
147 unset($error['backtrace']); | |
148 | |
149 // When running inside the testing framework, we relay the errors | |
150 // to the tested site by the way of HTTP headers. | |
151 if (DRUPAL_TEST_IN_CHILD_SITE && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) { | |
152 _drupal_error_header($error['@message'], $error['%type'], $error['%function'], $error['%file'], $error['%line']); | |
153 } | |
154 | |
155 $response = new Response(); | |
156 | |
157 // Only call the logger if there is a logger factory available. This can occur | |
158 // if there is an error while rebuilding the container or during the | |
159 // installer. | |
160 if (\Drupal::hasService('logger.factory')) { | |
161 try { | |
162 // Provide the PHP backtrace to logger implementations. | |
163 \Drupal::logger('php')->log($error['severity_level'], '%type: @message in %function (line %line of %file) @backtrace_string.', $error + ['backtrace' => $backtrace]); | |
164 } | |
165 catch (\Exception $e) { | |
166 // We can't log, for example because the database connection is not | |
167 // available. At least try to log to PHP error log. | |
168 error_log(strtr('Failed to log error: %type: @message in %function (line %line of %file). @backtrace_string', $error)); | |
169 } | |
170 } | |
171 | |
172 // Log fatal errors, so developers can find and debug them. | |
173 if ($fatal) { | |
174 error_log(sprintf('%s: %s in %s on line %d %s', $error['%type'], $error['@message'], $error['%file'], $error['%line'], $error['@backtrace_string'])); | |
175 } | |
176 | |
177 if (PHP_SAPI === 'cli') { | |
178 if ($fatal) { | |
179 // When called from CLI, simply output a plain text message. | |
180 // Should not translate the string to avoid errors producing more errors. | |
181 $response->setContent(html_entity_decode(strip_tags(SafeMarkup::format('%type: @message in %function (line %line of %file).', $error))) . "\n"); | |
182 $response->send(); | |
183 exit; | |
184 } | |
185 } | |
186 | |
187 if (\Drupal::hasRequest() && \Drupal::request()->isXmlHttpRequest()) { | |
188 if ($fatal) { | |
189 if (error_displayable($error)) { | |
190 // When called from JavaScript, simply output the error message. | |
191 // Should not translate the string to avoid errors producing more errors. | |
192 $response->setContent(SafeMarkup::format('%type: @message in %function (line %line of %file).', $error)); | |
193 $response->send(); | |
194 } | |
195 exit; | |
196 } | |
197 } | |
198 else { | |
199 // Display the message if the current error reporting level allows this type | |
200 // of message to be displayed, and unconditionally in update.php. | |
201 $message = ''; | |
202 $class = NULL; | |
203 if (error_displayable($error)) { | |
204 $class = 'error'; | |
205 | |
206 // If error type is 'User notice' then treat it as debug information | |
207 // instead of an error message. | |
208 // @see debug() | |
209 if ($error['%type'] == 'User notice') { | |
210 $error['%type'] = 'Debug'; | |
211 $class = 'status'; | |
212 } | |
213 | |
214 // Attempt to reduce verbosity by removing DRUPAL_ROOT from the file path | |
215 // in the message. This does not happen for (false) security. | |
216 if (\Drupal::hasService('app.root')) { | |
217 $root_length = strlen(\Drupal::root()); | |
218 if (substr($error['%file'], 0, $root_length) == \Drupal::root()) { | |
219 $error['%file'] = substr($error['%file'], $root_length + 1); | |
220 } | |
221 } | |
222 | |
223 // Check if verbose error reporting is on. | |
224 $error_level = _drupal_get_error_level(); | |
225 | |
226 if ($error_level != ERROR_REPORTING_DISPLAY_VERBOSE) { | |
227 // Without verbose logging, use a simple message. | |
228 | |
229 // We call SafeMarkup::format() directly here, rather than use t() since | |
230 // we are in the middle of error handling, and we don't want t() to | |
231 // cause further errors. | |
232 $message = SafeMarkup::format('%type: @message in %function (line %line of %file).', $error); | |
233 } | |
234 else { | |
235 // With verbose logging, we will also include a backtrace. | |
236 | |
237 // First trace is the error itself, already contained in the message. | |
238 // While the second trace is the error source and also contained in the | |
239 // message, the message doesn't contain argument values, so we output it | |
240 // once more in the backtrace. | |
241 array_shift($backtrace); | |
242 // Generate a backtrace containing only scalar argument values. | |
243 $error['@backtrace'] = Error::formatBacktrace($backtrace); | |
244 $message = SafeMarkup::format('%type: @message in %function (line %line of %file). <pre class="backtrace">@backtrace</pre>', $error); | |
245 } | |
246 } | |
247 | |
248 if ($fatal) { | |
249 // We fallback to a maintenance page at this point, because the page generation | |
250 // itself can generate errors. | |
251 // Should not translate the string to avoid errors producing more errors. | |
252 $message = 'The website encountered an unexpected error. Please try again later.' . '<br />' . $message; | |
253 | |
254 if ($is_installer) { | |
255 // install_display_output() prints the output and ends script execution. | |
256 $output = [ | |
257 '#title' => 'Error', | |
258 '#markup' => $message, | |
259 ]; | |
260 install_display_output($output, $GLOBALS['install_state'], $response->headers->all()); | |
261 exit; | |
262 } | |
263 | |
264 $response->setContent($message); | |
265 $response->setStatusCode(500, '500 Service unavailable (with message)'); | |
266 | |
267 $response->send(); | |
268 // An exception must halt script execution. | |
269 exit; | |
270 } | |
271 | |
272 if ($message) { | |
273 if (\Drupal::hasService('session')) { | |
274 // Message display is dependent on sessions being available. | |
275 drupal_set_message($message, $class, TRUE); | |
276 } | |
277 else { | |
278 print $message; | |
279 } | |
280 } | |
281 } | |
282 } | |
283 | |
284 /** | |
285 * Returns the current error level. | |
286 * | |
287 * This function should only be used to get the current error level prior to the | |
288 * kernel being booted or before Drupal is installed. In all other situations | |
289 * the following code is preferred: | |
290 * @code | |
291 * \Drupal::config('system.logging')->get('error_level'); | |
292 * @endcode | |
293 * | |
294 * @return string | |
295 * The current error level. | |
296 */ | |
297 function _drupal_get_error_level() { | |
298 // Raise the error level to maximum for the installer, so users are able to | |
299 // file proper bug reports for installer errors. The returned value is | |
300 // different to the one below, because the installer actually has a | |
301 // 'config.factory' service, which reads the default 'error_level' value from | |
302 // System module's default configuration and the default value is not verbose. | |
303 // @see error_displayable() | |
304 if (drupal_installation_attempted()) { | |
305 return ERROR_REPORTING_DISPLAY_VERBOSE; | |
306 } | |
307 $error_level = NULL; | |
308 // Try to get the error level configuration from database. If this fails, | |
309 // for example if the database connection is not there, try to read it from | |
310 // settings.php. | |
311 try { | |
312 $error_level = \Drupal::config('system.logging')->get('error_level'); | |
313 } | |
314 catch (\Exception $e) { | |
315 $error_level = isset($GLOBALS['config']['system.logging']['error_level']) ? $GLOBALS['config']['system.logging']['error_level'] : ERROR_REPORTING_HIDE; | |
316 } | |
317 | |
318 // If there is no container or if it has no config.factory service, we are | |
319 // possibly in an edge-case error situation while trying to serve a regular | |
320 // request on a public site, so use the non-verbose default value. | |
321 return $error_level ?: ERROR_REPORTING_DISPLAY_ALL; | |
322 } | |
323 | |
324 /** | |
325 * Adds error information to headers so that tests can access it. | |
326 * | |
327 * @param $message | |
328 * The error message. | |
329 * @param $type | |
330 * The type of error. | |
331 * @param $function | |
332 * The function that emitted the error. | |
333 * @param $file | |
334 * The file that emitted the error. | |
335 * @param $line | |
336 * The line number in file that emitted the error. | |
337 */ | |
338 function _drupal_error_header($message, $type, $function, $file, $line) { | |
339 // $number does not use drupal_static as it should not be reset | |
340 // as it uniquely identifies each PHP error. | |
341 static $number = 0; | |
342 $assertion = [ | |
343 $message, | |
344 $type, | |
345 [ | |
346 'function' => $function, | |
347 'file' => $file, | |
348 'line' => $line, | |
349 ], | |
350 ]; | |
351 // For non-fatal errors (e.g. PHP notices) _drupal_log_error can be called | |
352 // multiple times per request. In that case the response is typically | |
353 // generated outside of the error handler, e.g., in a controller. As a | |
354 // result it is not possible to use a Response object here but instead the | |
355 // headers need to be emitted directly. | |
356 header('X-Drupal-Assertion-' . $number . ': ' . rawurlencode(serialize($assertion))); | |
357 $number++; | |
358 } |