| Drupal | PHP Cross Reference | Content Management Systems |
1 <?php 2 3 /** 4 * @file 5 * Functions for error handling. 6 */ 7 8 /** 9 * Maps PHP error constants to watchdog severity levels. 10 * 11 * The error constants are documented at 12 * http://php.net/manual/en/errorfunc.constants.php 13 * 14 * @ingroup logging_severity_levels 15 */ 16 function drupal_error_levels() { 17 $types = array( 18 E_ERROR => array('Error', WATCHDOG_ERROR), 19 E_WARNING => array('Warning', WATCHDOG_WARNING), 20 E_PARSE => array('Parse error', WATCHDOG_ERROR), 21 E_NOTICE => array('Notice', WATCHDOG_NOTICE), 22 E_CORE_ERROR => array('Core error', WATCHDOG_ERROR), 23 E_CORE_WARNING => array('Core warning', WATCHDOG_WARNING), 24 E_COMPILE_ERROR => array('Compile error', WATCHDOG_ERROR), 25 E_COMPILE_WARNING => array('Compile warning', WATCHDOG_WARNING), 26 E_USER_ERROR => array('User error', WATCHDOG_ERROR), 27 E_USER_WARNING => array('User warning', WATCHDOG_WARNING), 28 E_USER_NOTICE => array('User notice', WATCHDOG_NOTICE), 29 E_STRICT => array('Strict warning', WATCHDOG_DEBUG), 30 E_RECOVERABLE_ERROR => array('Recoverable fatal error', WATCHDOG_ERROR), 31 ); 32 // E_DEPRECATED and E_USER_DEPRECATED were added in PHP 5.3.0. 33 if (defined('E_DEPRECATED')) { 34 $types[E_DEPRECATED] = array('Deprecated function', WATCHDOG_DEBUG); 35 $types[E_USER_DEPRECATED] = array('User deprecated function', WATCHDOG_DEBUG); 36 } 37 return $types; 38 } 39 40 /** 41 * Provides custom PHP error handling. 42 * 43 * @param $error_level 44 * The level of the error raised. 45 * @param $message 46 * The error message. 47 * @param $filename 48 * The filename that the error was raised in. 49 * @param $line 50 * The line number the error was raised at. 51 * @param $context 52 * An array that points to the active symbol table at the point the error 53 * occurred. 54 */ 55 function _drupal_error_handler_real($error_level, $message, $filename, $line, $context) { 56 if ($error_level & error_reporting()) { 57 $types = drupal_error_levels(); 58 list($severity_msg, $severity_level) = $types[$error_level]; 59 $caller = _drupal_get_last_caller(debug_backtrace()); 60 61 if (!function_exists('filter_xss_admin')) { 62 require_once DRUPAL_ROOT . '/includes/common.inc'; 63 } 64 65 // We treat recoverable errors as fatal. 66 _drupal_log_error(array( 67 '%type' => isset($types[$error_level]) ? $severity_msg : 'Unknown error', 68 // The standard PHP error handler considers that the error messages 69 // are HTML. We mimick this behavior here. 70 '!message' => filter_xss_admin($message), 71 '%function' => $caller['function'], 72 '%file' => $caller['file'], 73 '%line' => $caller['line'], 74 'severity_level' => $severity_level, 75 ), $error_level == E_RECOVERABLE_ERROR); 76 } 77 } 78 79 /** 80 * Decodes an exception and retrieves the correct caller. 81 * 82 * @param $exception 83 * The exception object that was thrown. 84 * 85 * @return 86 * An error in the format expected by _drupal_log_error(). 87 */ 88 function _drupal_decode_exception($exception) { 89 $message = $exception->getMessage(); 90 91 $backtrace = $exception->getTrace(); 92 // Add the line throwing the exception to the backtrace. 93 array_unshift($backtrace, array('line' => $exception->getLine(), 'file' => $exception->getFile())); 94 95 // For PDOException errors, we try to return the initial caller, 96 // skipping internal functions of the database layer. 97 if ($exception instanceof PDOException) { 98 // The first element in the stack is the call, the second element gives us the caller. 99 // We skip calls that occurred in one of the classes of the database layer 100 // or in one of its global functions. 101 $db_functions = array('db_query', 'db_query_range'); 102 while (!empty($backtrace[1]) && ($caller = $backtrace[1]) && 103 ((isset($caller['class']) && (strpos($caller['class'], 'Query') !== FALSE || strpos($caller['class'], 'Database') !== FALSE || strpos($caller['class'], 'PDO') !== FALSE)) || 104 in_array($caller['function'], $db_functions))) { 105 // We remove that call. 106 array_shift($backtrace); 107 } 108 if (isset($exception->query_string, $exception->args)) { 109 $message .= ": " . $exception->query_string . "; " . print_r($exception->args, TRUE); 110 } 111 } 112 $caller = _drupal_get_last_caller($backtrace); 113 114 return array( 115 '%type' => get_class($exception), 116 // The standard PHP exception handler considers that the exception message 117 // is plain-text. We mimick this behavior here. 118 '!message' => check_plain($message), 119 '%function' => $caller['function'], 120 '%file' => $caller['file'], 121 '%line' => $caller['line'], 122 'severity_level' => WATCHDOG_ERROR, 123 ); 124 } 125 126 /** 127 * Renders an exception error message without further exceptions. 128 * 129 * @param $exception 130 * The exception object that was thrown. 131 * @return 132 * An error message. 133 */ 134 function _drupal_render_exception_safe($exception) { 135 return check_plain(strtr('%type: !message in %function (line %line of %file).', _drupal_decode_exception($exception))); 136 } 137 138 /** 139 * Determines whether an error should be displayed. 140 * 141 * When in maintenance mode or when error_level is ERROR_REPORTING_DISPLAY_ALL, 142 * all errors should be displayed. For ERROR_REPORTING_DISPLAY_SOME, $error 143 * will be examined to determine if it should be displayed. 144 * 145 * @param $error 146 * Optional error to examine for ERROR_REPORTING_DISPLAY_SOME. 147 * 148 * @return 149 * TRUE if an error should be displayed. 150 */ 151 function error_displayable($error = NULL) { 152 $error_level = variable_get('error_level', ERROR_REPORTING_DISPLAY_ALL); 153 $updating = (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update'); 154 $all_errors_displayed = ($error_level == ERROR_REPORTING_DISPLAY_ALL); 155 $error_needs_display = ($error_level == ERROR_REPORTING_DISPLAY_SOME && 156 isset($error) && $error['%type'] != 'Notice' && $error['%type'] != 'Strict warning'); 157 158 return ($updating || $all_errors_displayed || $error_needs_display); 159 } 160 161 /** 162 * Logs a PHP error or exception and displays an error page in fatal cases. 163 * 164 * @param $error 165 * An array with the following keys: %type, !message, %function, %file, %line 166 * and severity_level. All the parameters are plain-text, with the exception 167 * of !message, which needs to be a safe HTML string. 168 * @param $fatal 169 * TRUE if the error is fatal. 170 */ 171 function _drupal_log_error($error, $fatal = FALSE) { 172 // Initialize a maintenance theme if the boostrap was not complete. 173 // Do it early because drupal_set_message() triggers a drupal_theme_initialize(). 174 if ($fatal && (drupal_get_bootstrap_phase() != DRUPAL_BOOTSTRAP_FULL)) { 175 unset($GLOBALS['theme']); 176 if (!defined('MAINTENANCE_MODE')) { 177 define('MAINTENANCE_MODE', 'error'); 178 } 179 drupal_maintenance_theme(); 180 } 181 182 // When running inside the testing framework, we relay the errors 183 // to the tested site by the way of HTTP headers. 184 $test_info = &$GLOBALS['drupal_test_info']; 185 if (!empty($test_info['in_child_site']) && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) { 186 // $number does not use drupal_static as it should not be reset 187 // as it uniquely identifies each PHP error. 188 static $number = 0; 189 $assertion = array( 190 $error['!message'], 191 $error['%type'], 192 array( 193 'function' => $error['%function'], 194 'file' => $error['%file'], 195 'line' => $error['%line'], 196 ), 197 ); 198 header('X-Drupal-Assertion-' . $number . ': ' . rawurlencode(serialize($assertion))); 199 $number++; 200 } 201 202 watchdog('php', '%type: !message in %function (line %line of %file).', $error, $error['severity_level']); 203 204 if ($fatal) { 205 drupal_add_http_header('Status', '500 Service unavailable (with message)'); 206 } 207 208 if (drupal_is_cli()) { 209 if ($fatal) { 210 // When called from CLI, simply output a plain text message. 211 print html_entity_decode(strip_tags(t('%type: !message in %function (line %line of %file).', $error))). "\n"; 212 exit; 213 } 214 } 215 216 if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') { 217 if ($fatal) { 218 if (error_displayable($error)) { 219 // When called from JavaScript, simply output the error message. 220 print t('%type: !message in %function (line %line of %file).', $error); 221 } 222 exit; 223 } 224 } 225 else { 226 // Display the message if the current error reporting level allows this type 227 // of message to be displayed, and unconditionnaly in update.php. 228 if (error_displayable($error)) { 229 $class = 'error'; 230 231 // If error type is 'User notice' then treat it as debug information 232 // instead of an error message, see dd(). 233 if ($error['%type'] == 'User notice') { 234 $error['%type'] = 'Debug'; 235 $class = 'status'; 236 } 237 238 drupal_set_message(t('%type: !message in %function (line %line of %file).', $error), $class); 239 } 240 241 if ($fatal) { 242 drupal_set_title(t('Error')); 243 // We fallback to a maintenance page at this point, because the page generation 244 // itself can generate errors. 245 print theme('maintenance_page', array('content' => t('The website encountered an unexpected error. Please try again later.'))); 246 exit; 247 } 248 } 249 } 250 251 /** 252 * Gets the last caller from a backtrace. 253 * 254 * @param $backtrace 255 * A standard PHP backtrace. 256 * 257 * @return 258 * An associative array with keys 'file', 'line' and 'function'. 259 */ 260 function _drupal_get_last_caller($backtrace) { 261 // Errors that occur inside PHP internal functions do not generate 262 // information about file and line. Ignore black listed functions. 263 $blacklist = array('debug', '_drupal_error_handler', '_drupal_exception_handler'); 264 while (($backtrace && !isset($backtrace[0]['line'])) || 265 (isset($backtrace[1]['function']) && in_array($backtrace[1]['function'], $blacklist))) { 266 array_shift($backtrace); 267 } 268 269 // The first trace is the call itself. 270 // It gives us the line and the file of the last call. 271 $call = $backtrace[0]; 272 273 // The second call give us the function where the call originated. 274 if (isset($backtrace[1])) { 275 if (isset($backtrace[1]['class'])) { 276 $call['function'] = $backtrace[1]['class'] . $backtrace[1]['type'] . $backtrace[1]['function'] . '()'; 277 } 278 else { 279 $call['function'] = $backtrace[1]['function'] . '()'; 280 } 281 } 282 else { 283 $call['function'] = 'main()'; 284 } 285 return $call; 286 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
title