| 1 |
<?php |
| 2 |
|
| 3 |
/** |
| 4 |
* Provides low-level debugging, error and exception functionality |
| 5 |
* |
| 6 |
* @copyright Copyright (c) 2007-2011 Will Bond, others |
| 7 |
* @author Will Bond [wb] <will@flourishlib.com> |
| 8 |
* @author Will Bond, iMarc LLC [wb-imarc] <will@imarc.net> |
| 9 |
* @author Nick Trew [nt] |
| 10 |
* @author Kevin Hamer [kh] <kevin@imarc.net> |
| 11 |
* @author Jeff Turcotte [jt] <jeff@imarc.net> |
| 12 |
* @license http://flourishlib.com/license |
| 13 |
* |
| 14 |
* @package Flourish |
| 15 |
* @link http://flourishlib.com/fCore |
| 16 |
* |
| 17 |
* @version 1.0.0b26 |
| 18 |
* @changes 1.0.0b26 Added handle_fatal_errors flag to enableErrorHandling [jt, 2013-06-10] |
| 19 |
* @changes 1.0.0b25 exposing ->generateContext() [kh, 2012-12-18] |
| 20 |
* @changes 1.0.0b24 Backwards Compatibility Break - moved ::detectOpcodeCache() to fLoader::hasOpcodeCache() [wb, 2011-08-26] |
| 21 |
* @changes 1.0.0b23 Backwards Compatibility Break - changed the email subject of error/exception emails to include relevant file info, instead of the timestamp, for better email message threading [wb, 2011-06-20] |
| 22 |
* @changes 1.0.0b22 Fixed a bug with dumping arrays containing integers [wb, 2011-05-26] |
| 23 |
* @changes 1.0.0b21 Changed ::startErrorCapture() to allow "stacking" it via multiple calls, fixed a couple of bugs with ::dump() mangling strings in the form `int(1)`, fixed mispelling of `occurred` [wb, 2011-05-09] |
| 24 |
* @changes 1.0.0b20 Backwards Compatibility Break - Updated ::expose() to not wrap the data in HTML when running via CLI, and instead just append a newline [wb, 2011-02-24] |
| 25 |
* @changes 1.0.0b19 Added detection of AIX to ::checkOS() [wb, 2011-01-19] |
| 26 |
* @changes 1.0.0b18 Updated ::expose() to be able to accept multiple parameters [wb, 2011-01-10] |
| 27 |
* @changes 1.0.0b17 Fixed a bug with ::backtrace() triggering notices when an argument is not UTF-8 [wb, 2010-08-17] |
| 28 |
* @changes 1.0.0b16 Added the `$types` and `$regex` parameters to ::startErrorCapture() and the `$regex` parameter to ::stopErrorCapture() [wb, 2010-08-09] |
| 29 |
* @changes 1.0.0b15 Added ::startErrorCapture() and ::stopErrorCapture() [wb, 2010-07-05] |
| 30 |
* @changes 1.0.0b14 Changed ::enableExceptionHandling() to only call fException::printMessage() when the destination is not `html` and no callback has been defined, added ::configureSMTP() to allow using fSMTP for error and exception emails [wb, 2010-06-04] |
| 31 |
* @changes 1.0.0b13 Added the `$backtrace` parameter to ::backtrace() [wb, 2010-03-05] |
| 32 |
* @changes 1.0.0b12 Added ::getDebug() to check for the global debugging flag, added more specific BSD checks to ::checkOS() [wb, 2010-03-02] |
| 33 |
* @changes 1.0.0b11 Added ::detectOpcodeCache() [nt+wb, 2009-10-06] |
| 34 |
* @changes 1.0.0b10 Fixed ::expose() to properly display when output includes non-UTF-8 binary data [wb, 2009-06-29] |
| 35 |
* @changes 1.0.0b9 Added ::disableContext() to remove context info for exception/error handling, tweaked output for exceptions/errors [wb, 2009-06-28] |
| 36 |
* @changes 1.0.0b8 ::enableErrorHandling() and ::enableExceptionHandling() now accept multiple email addresses, and a much wider range of emails [wb-imarc, 2009-06-01] |
| 37 |
* @changes 1.0.0b7 ::backtrace() now properly replaces document root with {doc_root} on Windows [wb, 2009-05-02] |
| 38 |
* @changes 1.0.0b6 Fixed a bug with getting the server name for error messages when running on the command line [wb, 2009-03-11] |
| 39 |
* @changes 1.0.0b5 Fixed a bug with checking the error/exception destination when a log file is specified [wb, 2009-03-07] |
| 40 |
* @changes 1.0.0b4 Backwards compatibility break - ::getOS() and ::getPHPVersion() removed, replaced with ::checkOS() and ::checkVersion() [wb, 2009-02-16] |
| 41 |
* @changes 1.0.0b3 ::handleError() now displays what kind of error occurred as the heading [wb, 2009-02-15] |
| 42 |
* @changes 1.0.0b2 Added ::registerDebugCallback() [wb, 2009-02-07] |
| 43 |
* @changes 1.0.0b The initial implementation [wb, 2007-09-25] |
| 44 |
*/ |
| 45 |
class fCore { |
| 46 |
|
| 47 |
// The following constants allow for nice looking callbacks to static methods |
| 48 |
const backtrace = 'fCore::backtrace'; |
| 49 |
const call = 'fCore::call'; |
| 50 |
const callback = 'fCore::callback'; |
| 51 |
const checkOS = 'fCore::checkOS'; |
| 52 |
const checkVersion = 'fCore::checkVersion'; |
| 53 |
const configureSMTP = 'fCore::configureSMTP'; |
| 54 |
const debug = 'fCore::debug'; |
| 55 |
const disableContext = 'fCore::disableContext'; |
| 56 |
const dump = 'fCore::dump'; |
| 57 |
const enableDebugging = 'fCore::enableDebugging'; |
| 58 |
const enableDynamicConstants = 'fCore::enableDynamicConstants'; |
| 59 |
const enableErrorHandling = 'fCore::enableErrorHandling'; |
| 60 |
const enableExceptionHandling = 'fCore::enableExceptionHandling'; |
| 61 |
const expose = 'fCore::expose'; |
| 62 |
const getDebug = 'fCore::getDebug'; |
| 63 |
const handleError = 'fCore::handleError'; |
| 64 |
const handleFatalError = 'fCore::handleFatalError'; |
| 65 |
const handleException = 'fCore::handleException'; |
| 66 |
const registerDebugCallback = 'fCore::registerDebugCallback'; |
| 67 |
const reset = 'fCore::reset'; |
| 68 |
const sendMessagesOnShutdown = 'fCore::sendMessagesOnShutdown'; |
| 69 |
const startErrorCapture = 'fCore::startErrorCapture'; |
| 70 |
const stopErrorCapture = 'fCore::stopErrorCapture'; |
| 71 |
|
| 72 |
/** |
| 73 |
* The nesting level of error capturing |
| 74 |
* |
| 75 |
* @var integer |
| 76 |
*/ |
| 77 |
static private $captured_error_level = 0; |
| 78 |
|
| 79 |
/** |
| 80 |
* A stack of regex to match errors to capture, one string per level |
| 81 |
* |
| 82 |
* @var array |
| 83 |
*/ |
| 84 |
static private $captured_error_regex = array(); |
| 85 |
|
| 86 |
/** |
| 87 |
* A stack of the types of errors to capture, one integer per level |
| 88 |
* |
| 89 |
* @var array |
| 90 |
*/ |
| 91 |
static private $captured_error_types = array(); |
| 92 |
|
| 93 |
/** |
| 94 |
* A stack of arrays of errors that have been captured, one array per level |
| 95 |
* |
| 96 |
* @var array |
| 97 |
*/ |
| 98 |
static private $captured_errors = array(); |
| 99 |
|
| 100 |
/** |
| 101 |
* A stack of the previous error handler, one callback per level |
| 102 |
* |
| 103 |
* @var array |
| 104 |
*/ |
| 105 |
static private $captured_errors_previous_handler = array(); |
| 106 |
|
| 107 |
/** |
| 108 |
* If the context info has been shown |
| 109 |
* |
| 110 |
* @var boolean |
| 111 |
*/ |
| 112 |
static private $context_shown = FALSE; |
| 113 |
|
| 114 |
/** |
| 115 |
* If global debugging is enabled |
| 116 |
* |
| 117 |
* @var boolean |
| 118 |
*/ |
| 119 |
static private $debug = NULL; |
| 120 |
|
| 121 |
/** |
| 122 |
* A callback to pass debug messages to |
| 123 |
* |
| 124 |
* @var callback |
| 125 |
*/ |
| 126 |
static private $debug_callback = NULL; |
| 127 |
|
| 128 |
/** |
| 129 |
* If dynamic constants should be created |
| 130 |
* |
| 131 |
* @var boolean |
| 132 |
*/ |
| 133 |
static private $dynamic_constants = FALSE; |
| 134 |
|
| 135 |
/** |
| 136 |
* Error destination |
| 137 |
* |
| 138 |
* @var string |
| 139 |
*/ |
| 140 |
static private $error_destination = 'html'; |
| 141 |
|
| 142 |
/** |
| 143 |
* An array of errors to be send to the destination upon page completion |
| 144 |
* |
| 145 |
* @var array |
| 146 |
*/ |
| 147 |
static private $error_message_queue = array(); |
| 148 |
|
| 149 |
/** |
| 150 |
* Exception destination |
| 151 |
* |
| 152 |
* @var string |
| 153 |
*/ |
| 154 |
static private $exception_destination = 'html'; |
| 155 |
|
| 156 |
/** |
| 157 |
* Exception handler callback |
| 158 |
* |
| 159 |
* @var mixed |
| 160 |
*/ |
| 161 |
static private $exception_handler_callback = NULL; |
| 162 |
|
| 163 |
/** |
| 164 |
* Exception handler callback parameters |
| 165 |
* |
| 166 |
* @var array |
| 167 |
*/ |
| 168 |
static private $exception_handler_parameters = array(); |
| 169 |
|
| 170 |
/** |
| 171 |
* The message generated by the uncaught exception |
| 172 |
* |
| 173 |
* @var string |
| 174 |
*/ |
| 175 |
static private $exception_message = NULL; |
| 176 |
|
| 177 |
/** |
| 178 |
* If this class is handling errors |
| 179 |
* |
| 180 |
* @var boolean |
| 181 |
*/ |
| 182 |
static private $handles_errors = FALSE; |
| 183 |
|
| 184 |
/** |
| 185 |
* If this class is handling exceptions |
| 186 |
* |
| 187 |
* @var boolean |
| 188 |
*/ |
| 189 |
static private $handles_exceptions = FALSE; |
| 190 |
|
| 191 |
/** |
| 192 |
* If the context info should be shown with errors/exceptions |
| 193 |
* |
| 194 |
* @var boolean |
| 195 |
*/ |
| 196 |
static private $show_context = TRUE; |
| 197 |
|
| 198 |
/** |
| 199 |
* An array of the most significant lines from error and exception backtraces |
| 200 |
* |
| 201 |
* @var array |
| 202 |
*/ |
| 203 |
static private $significant_error_lines = array(); |
| 204 |
|
| 205 |
/** |
| 206 |
* An SMTP connection for sending error and exception emails |
| 207 |
* |
| 208 |
* @var fSMTP |
| 209 |
*/ |
| 210 |
static private $smtp_connection = NULL; |
| 211 |
|
| 212 |
/** |
| 213 |
* The email address to send error emails from |
| 214 |
* |
| 215 |
* @var string |
| 216 |
*/ |
| 217 |
static private $smtp_from_email = NULL; |
| 218 |
|
| 219 |
/** |
| 220 |
* Creates a nicely formatted backtrace to the the point where this method is called |
| 221 |
* |
| 222 |
* @param integer $remove_lines The number of trailing lines to remove from the backtrace |
| 223 |
* @param array $backtrace A backtrace from [http://php.net/backtrace `debug_backtrace()`] to format - this is not usually required or desired |
| 224 |
* @return string The formatted backtrace |
| 225 |
*/ |
| 226 |
static public function backtrace($remove_lines = 0, $backtrace = NULL) { |
| 227 |
$args = func_get_args(); |
| 228 |
$remove_lines_val = isset($args[0]) ? $args[0] : 0; |
| 229 |
$backtrace_val = isset($args[1]) ? $args[1] : NULL; |
| 230 |
|
| 231 |
$doc_root = realpath($_SERVER['DOCUMENT_ROOT']); |
| 232 |
$doc_root .= (substr($doc_root, -1) != DIRECTORY_SEPARATOR) ? DIRECTORY_SEPARATOR : ''; |
| 233 |
|
| 234 |
if ($backtrace_val === NULL) { |
| 235 |
$backtrace_val = debug_backtrace(); |
| 236 |
} |
| 237 |
|
| 238 |
if ($remove_lines_val !== NULL && !is_numeric($remove_lines_val)) { |
| 239 |
$remove_lines_val = 0; |
| 240 |
} |
| 241 |
|
| 242 |
settype($remove_lines_val, 'integer'); |
| 243 |
|
| 244 |
while ($remove_lines_val > 0) { |
| 245 |
array_shift($backtrace_val); |
| 246 |
$remove_lines_val--; |
| 247 |
} |
| 248 |
|
| 249 |
$backtrace_val = array_reverse($backtrace_val); |
| 250 |
|
| 251 |
$bt_string = ''; |
| 252 |
$i = 0; |
| 253 |
foreach ($backtrace_val as $call) { |
| 254 |
if ($i) { |
| 255 |
$bt_string .= "\n"; |
| 256 |
} |
| 257 |
if (isset($call['file'])) { |
| 258 |
$bt_string .= str_replace($doc_root, '{doc_root}' . DIRECTORY_SEPARATOR, $call['file']) . '(' . $call['line'] . '): '; |
| 259 |
} else { |
| 260 |
$bt_string .= '[internal function]: '; |
| 261 |
} |
| 262 |
if (isset($call['class'])) { |
| 263 |
$bt_string .= $call['class'] . $call['type']; |
| 264 |
} |
| 265 |
if (isset($call['class']) || isset($call['function'])) { |
| 266 |
$bt_string .= $call['function'] . '('; |
| 267 |
$j = 0; |
| 268 |
if (!isset($call['args'])) { |
| 269 |
$call['args'] = array(); |
| 270 |
} |
| 271 |
foreach ($call['args'] as $arg) { |
| 272 |
if ($j) { |
| 273 |
$bt_string .= ', '; |
| 274 |
} |
| 275 |
if (is_bool($arg)) { |
| 276 |
$bt_string .= ($arg) ? 'true' : 'false'; |
| 277 |
} elseif (is_null($arg)) { |
| 278 |
$bt_string .= 'NULL'; |
| 279 |
} elseif (is_array($arg)) { |
| 280 |
$bt_string .= 'Array'; |
| 281 |
} elseif (is_object($arg)) { |
| 282 |
$bt_string .= 'Object(' . get_class($arg) . ')'; |
| 283 |
} elseif (is_string($arg)) { |
| 284 |
// Shorten the UTF-8 string if it is too long |
| 285 |
if (strlen(utf8_decode($arg)) > 18) { |
| 286 |
// If we can't match as unicode, try single byte |
| 287 |
if (!preg_match('#^(.{0,15})#us', $arg, $short_arg)) { |
| 288 |
preg_match('#^(.{0,15})#s', $arg, $short_arg); |
| 289 |
} |
| 290 |
$arg = $short_arg[0] . '...'; |
| 291 |
} |
| 292 |
$bt_string .= "'" . $arg . "'"; |
| 293 |
} else { |
| 294 |
$bt_string .= (string) $arg; |
| 295 |
} |
| 296 |
$j++; |
| 297 |
} |
| 298 |
$bt_string .= ')'; |
| 299 |
} |
| 300 |
$i++; |
| 301 |
} |
| 302 |
|
| 303 |
return $bt_string; |
| 304 |
} |
| 305 |
|
| 306 |
/** |
| 307 |
* Performs a [http://php.net/call_user_func call_user_func()], while translating PHP 5.2 static callback syntax for PHP 5.1 and 5.0 |
| 308 |
* |
| 309 |
* Parameters can be passed either as a single array of parameters or as |
| 310 |
* multiple parameters. |
| 311 |
* |
| 312 |
* {{{ |
| 313 |
* #!php |
| 314 |
* // Passing multiple parameters in a normal fashion |
| 315 |
* fCore::call('Class::method', TRUE, 0, 'test'); |
| 316 |
* |
| 317 |
* // Passing multiple parameters in a parameters array |
| 318 |
* fCore::call('Class::method', array(TRUE, 0, 'test')); |
| 319 |
* }}} |
| 320 |
* |
| 321 |
* To pass parameters by reference they must be assigned to an |
| 322 |
* array by reference and the function/method being called must accept those |
| 323 |
* parameters by reference. If either condition is not met, the parameter |
| 324 |
* will be passed by value. |
| 325 |
* |
| 326 |
* {{{ |
| 327 |
* #!php |
| 328 |
* // Passing parameters by reference |
| 329 |
* fCore::call('Class::method', array(&$var1, &$var2)); |
| 330 |
* }}} |
| 331 |
* |
| 332 |
* @param callback $callback The function or method to call |
| 333 |
* @param array $parameters The parameters to pass to the function/method |
| 334 |
* @return mixed The return value of the called function/method |
| 335 |
*/ |
| 336 |
static public function call($callback, $parameters = array()) { |
| 337 |
// Fix PHP 5.0 and 5.1 static callback syntax |
| 338 |
if (is_string($callback) && strpos($callback, '::') !== FALSE) { |
| 339 |
$callback = explode('::', $callback); |
| 340 |
} |
| 341 |
|
| 342 |
$parameters = array_slice(func_get_args(), 1); |
| 343 |
if (sizeof($parameters) == 1 && is_array($parameters[0])) { |
| 344 |
$parameters = $parameters[0]; |
| 345 |
} |
| 346 |
|
| 347 |
return call_user_func_array($callback, $parameters); |
| 348 |
} |
| 349 |
|
| 350 |
/** |
| 351 |
* Translates a Class::method style static method callback to array style for compatibility with PHP 5.0 and 5.1 and built-in PHP functions |
| 352 |
* |
| 353 |
* @param callback $callback The callback to translate |
| 354 |
* @return array The translated callback |
| 355 |
*/ |
| 356 |
static public function callback($callback) { |
| 357 |
if (is_string($callback) && strpos($callback, '::') !== FALSE) { |
| 358 |
return explode('::', $callback); |
| 359 |
} |
| 360 |
|
| 361 |
return $callback; |
| 362 |
} |
| 363 |
|
| 364 |
/** |
| 365 |
* Checks an error/exception destination to make sure it is valid |
| 366 |
* |
| 367 |
* @param string $destination The destination for the exception. An email, file or the string `'html'`. |
| 368 |
* @return string|boolean `'email'`, `'file'`, `'html'` or `FALSE` |
| 369 |
*/ |
| 370 |
static private function checkDestination($destination) { |
| 371 |
if ($destination == 'html') { |
| 372 |
return 'html'; |
| 373 |
} |
| 374 |
|
| 375 |
if (preg_match('~^(?: # Allow leading whitespace |
| 376 |
(?:[^\x00-\x20\(\)<>@,;:\\\\"\.\[\]]+|"[^"\\\\\n\r]+") # An "atom" or a quoted string |
| 377 |
(?:\.[ \t]*(?:[^\x00-\x20\(\)<>@,;:\\\\"\.\[\]]+|"[^"\\\\\n\r]+"[ \t]*))* # A . plus another "atom" or a quoted string, any number of times |
| 378 |
)@(?: # The @ symbol |
| 379 |
(?:[a-z0-9\\-]+\.)+[a-z]{2,}| # Domain name |
| 380 |
(?:(?:[01]?\d?\d|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d?\d|2[0-4]\d|25[0-5]) # (or) IP addresses |
| 381 |
) |
| 382 |
(?:\s*,\s* # Any number of other emails separated by a comma with surrounding spaces |
| 383 |
(?: |
| 384 |
(?:[^\x00-\x20\(\)<>@,;:\\\\"\.\[\]]+|"[^"\\\\\n\r]+") |
| 385 |
(?:\.[ \t]*(?:[^\x00-\x20\(\)<>@,;:\\\\"\.\[\]]+|"[^"\\\\\n\r]+"[ \t]*))* |
| 386 |
)@(?: |
| 387 |
(?:[a-z0-9\\-]+\.)+[a-z]{2,}| |
| 388 |
(?:(?:[01]?\d?\d|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d?\d|2[0-4]\d|25[0-5]) |
| 389 |
) |
| 390 |
)*$~xiD', $destination)) { |
| 391 |
return 'email'; |
| 392 |
} |
| 393 |
|
| 394 |
$path_info = pathinfo($destination); |
| 395 |
$dir_exists = file_exists($path_info['dirname']); |
| 396 |
$dir_writable = ($dir_exists) ? is_writable($path_info['dirname']) : FALSE; |
| 397 |
$file_exists = file_exists($destination); |
| 398 |
$file_writable = ($file_exists) ? is_writable($destination) : FALSE; |
| 399 |
|
| 400 |
if (!$dir_exists || ($dir_exists && ((!$file_exists && !$dir_writable) || ($file_exists && !$file_writable)))) { |
| 401 |
return FALSE; |
| 402 |
} |
| 403 |
|
| 404 |
return 'file'; |
| 405 |
} |
| 406 |
|
| 407 |
/** |
| 408 |
* Returns is the current OS is one of the OSes passed as a parameter |
| 409 |
* |
| 410 |
* Valid OS strings are: |
| 411 |
* - `'linux'` |
| 412 |
* - `'aix'` |
| 413 |
* - `'bsd'` |
| 414 |
* - `'freebsd'` |
| 415 |
* - `'netbsd'` |
| 416 |
* - `'openbsd'` |
| 417 |
* - `'osx'` |
| 418 |
* - `'solaris'` |
| 419 |
* - `'windows'` |
| 420 |
* |
| 421 |
* @param string $os The operating system to check - see method description for valid OSes |
| 422 |
* @param string ... |
| 423 |
* @return boolean If the current OS is included in the list of OSes passed as parameters |
| 424 |
*/ |
| 425 |
static public function checkOS($os) { |
| 426 |
$oses = func_get_args(); |
| 427 |
|
| 428 |
$valid_oses = array('linux', 'aix', 'bsd', 'freebsd', 'openbsd', 'netbsd', 'osx', 'solaris', 'windows'); |
| 429 |
|
| 430 |
if ($invalid_oses = array_diff($oses, $valid_oses)) { |
| 431 |
throw new fProgrammerException('One or more of the OSes specified, %$1s, is invalid. Must be one of: %2$s.', join(' ', $invalid_oses), join(', ', $valid_oses)); |
| 432 |
} |
| 433 |
|
| 434 |
$uname = PHP_OS;// php_uname('s'); |
| 435 |
DebugEcho("checkOS: $uname"); |
| 436 |
|
| 437 |
if (stripos($uname, 'linux') !== FALSE) { |
| 438 |
return in_array('linux', $oses); |
| 439 |
} elseif (stripos($uname, 'aix') !== FALSE) { |
| 440 |
return in_array('aix', $oses); |
| 441 |
} elseif (stripos($uname, 'netbsd') !== FALSE) { |
| 442 |
return in_array('netbsd', $oses) || in_array('bsd', $oses); |
| 443 |
} elseif (stripos($uname, 'openbsd') !== FALSE) { |
| 444 |
return in_array('openbsd', $oses) || in_array('bsd', $oses); |
| 445 |
} elseif (stripos($uname, 'freebsd') !== FALSE) { |
| 446 |
return in_array('freebsd', $oses) || in_array('bsd', $oses); |
| 447 |
} elseif (stripos($uname, 'solaris') !== FALSE || stripos($uname, 'sunos') !== FALSE) { |
| 448 |
return in_array('solaris', $oses); |
| 449 |
} elseif (stripos($uname, 'win') !== FALSE) { |
| 450 |
return in_array('windows', $oses); |
| 451 |
} elseif (stripos($uname, 'darwin') !== FALSE) { |
| 452 |
return in_array('osx', $oses); |
| 453 |
} |
| 454 |
|
| 455 |
//throw new fEnvironmentException("Unable to determine the current OS ($uname)"); |
| 456 |
return false; |
| 457 |
} |
| 458 |
|
| 459 |
/** |
| 460 |
* Checks to see if the running version of PHP is greater or equal to the version passed |
| 461 |
* |
| 462 |
* @return boolean If the running version of PHP is greater or equal to the version passed |
| 463 |
*/ |
| 464 |
static public function checkVersion($version) { |
| 465 |
static $running_version = NULL; |
| 466 |
|
| 467 |
if ($running_version === NULL) { |
| 468 |
$running_version = preg_replace('#^(\d+\.\d+\.\d+).*$#D', '\1', PHP_VERSION); |
| 469 |
} |
| 470 |
|
| 471 |
return version_compare($running_version, $version, '>='); |
| 472 |
} |
| 473 |
|
| 474 |
/** |
| 475 |
* Composes text using fText if loaded |
| 476 |
* |
| 477 |
* @param string $message The message to compose |
| 478 |
* @param mixed $component A string or number to insert into the message |
| 479 |
* @param mixed ... |
| 480 |
* @return string The composed and possible translated message |
| 481 |
*/ |
| 482 |
static private function compose($message) { |
| 483 |
$args = array_slice(func_get_args(), 1); |
| 484 |
|
| 485 |
if (class_exists('fText', FALSE)) { |
| 486 |
return call_user_func_array(array('fText', 'compose'), array($message, $args)); |
| 487 |
} else { |
| 488 |
return vsprintf($message, $args); |
| 489 |
} |
| 490 |
} |
| 491 |
|
| 492 |
/** |
| 493 |
* Sets an fSMTP object to be used for sending error and exception emails |
| 494 |
* |
| 495 |
* @param fSMTP $smtp The SMTP connection to send emails over |
| 496 |
* @param string $from_email The email address to use in the `From:` header |
| 497 |
* @return void |
| 498 |
*/ |
| 499 |
static public function configureSMTP($smtp, $from_email) { |
| 500 |
self::$smtp_connection = $smtp; |
| 501 |
self::$smtp_from_email = $from_email; |
| 502 |
} |
| 503 |
|
| 504 |
/** |
| 505 |
* Prints a debugging message if global or code-specific debugging is enabled |
| 506 |
* |
| 507 |
* @param string $message The debug message |
| 508 |
* @param boolean $force If debugging should be forced even when global debugging is off |
| 509 |
* @return void |
| 510 |
*/ |
| 511 |
static public function debug($message, $force = FALSE) { |
| 512 |
if ($force || self::$debug) { |
| 513 |
if (self::$debug_callback) { |
| 514 |
call_user_func(self::$debug_callback, $message); |
| 515 |
} else { |
| 516 |
self::expose($message); |
| 517 |
} |
| 518 |
} |
| 519 |
} |
| 520 |
|
| 521 |
/** |
| 522 |
* Creates a string representation of any variable using predefined strings for booleans, `NULL` and empty strings |
| 523 |
* |
| 524 |
* The string output format of this method is very similar to the output of |
| 525 |
* [http://php.net/print_r print_r()] except that the following values |
| 526 |
* are represented as special strings: |
| 527 |
* |
| 528 |
* - `TRUE`: `'{true}'` |
| 529 |
* - `FALSE`: `'{false}'` |
| 530 |
* - `NULL`: `'{null}'` |
| 531 |
* - `''`: `'{empty_string}'` |
| 532 |
* |
| 533 |
* @param mixed $data The value to dump |
| 534 |
* @return string The string representation of the value |
| 535 |
*/ |
| 536 |
static public function dump($data) { |
| 537 |
if (is_bool($data)) { |
| 538 |
return ($data) ? '{true}' : '{false}'; |
| 539 |
} elseif (is_null($data)) { |
| 540 |
return '{null}'; |
| 541 |
} elseif ($data === '') { |
| 542 |
return '{empty_string}'; |
| 543 |
} elseif (is_array($data) || is_object($data)) { |
| 544 |
|
| 545 |
ob_start(); |
| 546 |
var_dump($data); |
| 547 |
$output = ob_get_contents(); |
| 548 |
ob_end_clean(); |
| 549 |
|
| 550 |
// Make the var dump more like a print_r |
| 551 |
$output = preg_replace('#=>\n( )+(?=[a-zA-Z]|&)#m', ' => ', $output); |
| 552 |
$output = str_replace('string(0) ""', '{empty_string}', $output); |
| 553 |
$output = preg_replace('#=> (&)?NULL#', '=> \1{null}', $output); |
| 554 |
$output = preg_replace('#=> (&)?bool\((false|true)\)#', '=> \1{\2}', $output); |
| 555 |
$output = preg_replace('#(?<=^|\] => )(?:float|int)\((-?\d+(?:.\d+)?)\)#', '\1', $output); |
| 556 |
$output = preg_replace('#string\(\d+\) "#', '', $output); |
| 557 |
$output = preg_replace('#"(\n( )*)(?=\[|\})#', '\1', $output); |
| 558 |
$output = preg_replace('#((?: )+)\["(.*?)"\]#', '\1[\2]', $output); |
| 559 |
$output = preg_replace('#(?:&)?array\(\d+\) \{\n((?: )*)((?: )(?=\[)|(?=\}))#', "Array\n\\1(\n\\1\\2", $output); |
| 560 |
$output = preg_replace('/object\((\w+)\)#\d+ \(\d+\) {\n((?: )*)((?: )(?=\[)|(?=\}))/', "\\1 Object\n\\2(\n\\2\\3", $output); |
| 561 |
$output = preg_replace('#^((?: )+)}(?=\n|$)#m', "\\1)\n", $output); |
| 562 |
$output = substr($output, 0, -2) . ')'; |
| 563 |
|
| 564 |
// Fix indenting issues with the var dump output |
| 565 |
$output_lines = explode("\n", $output); |
| 566 |
$new_output = array(); |
| 567 |
$stack = 0; |
| 568 |
foreach ($output_lines as $line) { |
| 569 |
if (preg_match('#^((?: )*)([^ ])#', $line, $match)) { |
| 570 |
$spaces = strlen($match[1]); |
| 571 |
if ($spaces && $match[2] == '(') { |
| 572 |
$stack += 1; |
| 573 |
} |
| 574 |
$new_output[] = str_pad('', ($spaces) + (4 * $stack)) . $line; |
| 575 |
if ($spaces && $match[2] == ')') { |
| 576 |
$stack -= 1; |
| 577 |
} |
| 578 |
} else { |
| 579 |
$new_output[] = str_pad('', ($spaces) + (4 * $stack)) . $line; |
| 580 |
} |
| 581 |
} |
| 582 |
|
| 583 |
return join("\n", $new_output); |
| 584 |
} else { |
| 585 |
return (string) $data; |
| 586 |
} |
| 587 |
} |
| 588 |
|
| 589 |
/** |
| 590 |
* Disables including the context information with exception and error messages |
| 591 |
* |
| 592 |
* The context information includes the following superglobals: |
| 593 |
* |
| 594 |
* - `$_SERVER` |
| 595 |
* - `$_POST` |
| 596 |
* - `$_GET` |
| 597 |
* - `$_SESSION` |
| 598 |
* - `$_FILES` |
| 599 |
* - `$_COOKIE` |
| 600 |
* |
| 601 |
* @return void |
| 602 |
*/ |
| 603 |
static public function disableContext() { |
| 604 |
self::$show_context = FALSE; |
| 605 |
} |
| 606 |
|
| 607 |
/** |
| 608 |
* Enables debug messages globally, i.e. they will be shown for any call to ::debug() |
| 609 |
* |
| 610 |
* @param boolean $flag If debugging messages should be shown |
| 611 |
* @return void |
| 612 |
*/ |
| 613 |
static public function enableDebugging($flag) { |
| 614 |
self::$debug = (boolean) $flag; |
| 615 |
} |
| 616 |
|
| 617 |
/** |
| 618 |
* Turns on a feature where undefined constants are automatically created with the string value equivalent to the name |
| 619 |
* |
| 620 |
* This functionality only works if ::enableErrorHandling() has been |
| 621 |
* called first. This functionality may have a very slight performance |
| 622 |
* impact since a `E_STRICT` error message must be captured and then a |
| 623 |
* call to [http://php.net/define define()] is made. |
| 624 |
* |
| 625 |
* @return void |
| 626 |
*/ |
| 627 |
static public function enableDynamicConstants() { |
| 628 |
if (!self::$handles_errors) { |
| 629 |
throw new fProgrammerException('Dynamic constants can not be enabled unless error handling has been enabled via %s', __CLASS__ . '::enableErrorHandling()'); |
| 630 |
} |
| 631 |
self::$dynamic_constants = TRUE; |
| 632 |
} |
| 633 |
|
| 634 |
/** |
| 635 |
* Turns on developer-friendly error handling that includes context information including a backtrace and superglobal dumps |
| 636 |
* |
| 637 |
* All errors that match the current |
| 638 |
* [http://php.net/error_reporting error_reporting()] level will be |
| 639 |
* redirected to the destination and will include a full backtrace. In |
| 640 |
* addition, dumps of the following superglobals will be made to aid in |
| 641 |
* debugging: |
| 642 |
* |
| 643 |
* - `$_SERVER` |
| 644 |
* - `$_POST` |
| 645 |
* - `$_GET` |
| 646 |
* - `$_SESSION` |
| 647 |
* - `$_FILES` |
| 648 |
* - `$_COOKIE` |
| 649 |
* |
| 650 |
* The superglobal dumps are only done once per page, however a backtrace |
| 651 |
* in included for each error. |
| 652 |
* |
| 653 |
* If an email address is specified for the destination, only one email |
| 654 |
* will be sent per script execution. If both error and |
| 655 |
* [enableExceptionHandling() exception handling] are set to the same |
| 656 |
* email address, the email will contain both errors and exceptions. |
| 657 |
* |
| 658 |
* @param string $destination The destination for the errors and context information - an email address, a file path or the string `'html'` |
| 659 |
* @param boolean $handle_fatal_errors If true, a shutdown function will be registered to handle a fatal error. |
| 660 |
* Be aware, other shutdown functions could inadvertantly disable this one or exit the process. |
| 661 |
* |
| 662 |
* @return void |
| 663 |
*/ |
| 664 |
static public function enableErrorHandling($destination, $handle_fatal_errors = FALSE) { |
| 665 |
if (!self::checkDestination($destination)) { |
| 666 |
return; |
| 667 |
} |
| 668 |
self::$error_destination = $destination; |
| 669 |
self::$handles_errors = TRUE; |
| 670 |
set_error_handler(self::callback(self::handleError)); |
| 671 |
|
| 672 |
if ($handle_fatal_errors) { |
| 673 |
register_shutdown_function(self::callback(self::handleFatalError)); |
| 674 |
} |
| 675 |
} |
| 676 |
|
| 677 |
/** |
| 678 |
* Turns on developer-friendly uncaught exception handling that includes context information including a backtrace and superglobal dumps |
| 679 |
* |
| 680 |
* Any uncaught exception will be redirected to the destination specified, |
| 681 |
* and the page will execute the `$closing_code` callback before exiting. |
| 682 |
* The destination will receive a message with the exception messaage, a |
| 683 |
* full backtrace and dumps of the following superglobals to aid in |
| 684 |
* debugging: |
| 685 |
* |
| 686 |
* - `$_SERVER` |
| 687 |
* - `$_POST` |
| 688 |
* - `$_GET` |
| 689 |
* - `$_SESSION` |
| 690 |
* - `$_FILES` |
| 691 |
* - `$_COOKIE` |
| 692 |
* |
| 693 |
* The superglobal dumps are only done once per page, however a backtrace |
| 694 |
* in included for each error. |
| 695 |
* |
| 696 |
* If an email address is specified for the destination, only one email |
| 697 |
* will be sent per script execution. |
| 698 |
* |
| 699 |
* If an email address is specified for the destination, only one email |
| 700 |
* will be sent per script execution. If both exception and |
| 701 |
* [enableErrorHandling() error handling] are set to the same |
| 702 |
* email address, the email will contain both exceptions and errors. |
| 703 |
* |
| 704 |
* @param string $destination The destination for the exception and context information - an email address, a file path or the string `'html'` |
| 705 |
* @param callback $closing_code This callback will happen after the exception is handled and before page execution stops. Good for printing a footer. If no callback is provided and the exception extends fException, fException::printMessage() will be called. |
| 706 |
* @param array $parameters The parameters to send to `$closing_code` |
| 707 |
* @return void |
| 708 |
*/ |
| 709 |
static public function enableExceptionHandling($destination, $closing_code = NULL, $parameters = array()) { |
| 710 |
if (!self::checkDestination($destination)) { |
| 711 |
return; |
| 712 |
} |
| 713 |
self::$handles_exceptions = TRUE; |
| 714 |
self::$exception_destination = $destination; |
| 715 |
self::$exception_handler_callback = $closing_code; |
| 716 |
if (!is_object($parameters)) { |
| 717 |
settype($parameters, 'array'); |
| 718 |
} else { |
| 719 |
$parameters = array($parameters); |
| 720 |
} |
| 721 |
self::$exception_handler_parameters = $parameters; |
| 722 |
set_exception_handler(self::callback(self::handleException)); |
| 723 |
} |
| 724 |
|
| 725 |
/** |
| 726 |
* Prints the ::dump() of a value |
| 727 |
* |
| 728 |
* The dump will be printed in a `<pre>` tag with the class `exposed` if |
| 729 |
* PHP is running anywhere but via the command line (cli). If PHP is |
| 730 |
* running via the cli, the data will be printed, followed by a single |
| 731 |
* line break (`\n`). |
| 732 |
* |
| 733 |
* If multiple parameters are passed, they are exposed as an array. |
| 734 |
* |
| 735 |
* @param mixed $data The value to show |
| 736 |
* @param mixed ... |
| 737 |
* @return void |
| 738 |
*/ |
| 739 |
static public function expose($data) { |
| 740 |
$args = func_get_args(); |
| 741 |
if (count($args) > 1) { |
| 742 |
$data = $args; |
| 743 |
} |
| 744 |
if (PHP_SAPI != 'cli') { |
| 745 |
echo '<pre class="exposed">' . htmlspecialchars((string) self::dump($data), ENT_QUOTES) . '</pre>'; |
| 746 |
} else { |
| 747 |
echo self::dump($data) . "\n"; |
| 748 |
} |
| 749 |
} |
| 750 |
|
| 751 |
/** |
| 752 |
* Generates some information about the context of an error or exception |
| 753 |
* |
| 754 |
* @return string A string containing `$_SERVER`, `$_GET`, `$_POST`, `$_FILES`, `$_SESSION` and `$_COOKIE` |
| 755 |
*/ |
| 756 |
static public function generateContext() { |
| 757 |
return self::compose('Context') . "\n-------" . |
| 758 |
"\n\n\$_SERVER: " . self::dump($_SERVER) . |
| 759 |
"\n\n\$_POST: " . self::dump($_POST) . |
| 760 |
"\n\n\$_GET: " . self::dump($_GET) . |
| 761 |
"\n\n\$_FILES: " . self::dump($_FILES) . |
| 762 |
"\n\n\$_SESSION: " . self::dump((isset($_SESSION)) ? $_SESSION : NULL) . |
| 763 |
"\n\n\$_COOKIE: " . self::dump($_COOKIE); |
| 764 |
} |
| 765 |
|
| 766 |
/** |
| 767 |
* If debugging is enabled |
| 768 |
* |
| 769 |
* @param boolean $force If debugging is forced |
| 770 |
* @return boolean If debugging is enabled |
| 771 |
*/ |
| 772 |
static public function getDebug($force = FALSE) { |
| 773 |
return self::$debug || $force; |
| 774 |
} |
| 775 |
|
| 776 |
/** |
| 777 |
* A shutdown function to handle a fatal error |
| 778 |
* |
| 779 |
* @internal |
| 780 |
* |
| 781 |
* @return void |
| 782 |
*/ |
| 783 |
static public function handleFatalError() { |
| 784 |
$error = error_get_last(); |
| 785 |
|
| 786 |
$allowed_error_types = array( |
| 787 |
E_ERROR, E_CORE_ERROR |
| 788 |
); |
| 789 |
|
| 790 |
if ($error != NULL && in_array($error['type'], $allowed_error_types)) { |
| 791 |
$error_number = $error['type']; |
| 792 |
$error_file = $error['file']; |
| 793 |
$error_line = $error['line']; |
| 794 |
$error_string = $error['message']; |
| 795 |
|
| 796 |
self::handleError($error_number, $error_string, $error_file, $error_line); |
| 797 |
} |
| 798 |
} |
| 799 |
|
| 800 |
/** |
| 801 |
* Handles an error, creating the necessary context information and sending it to the specified destination |
| 802 |
* |
| 803 |
* @internal |
| 804 |
* |
| 805 |
* @param integer $error_number The error type |
| 806 |
* @param string $error_string The message for the error |
| 807 |
* @param string $error_file The file the error occurred in |
| 808 |
* @param integer $error_line The line the error occurred on |
| 809 |
* @param array $error_context A references to all variables in scope at the occurence of the error |
| 810 |
* @return void |
| 811 |
*/ |
| 812 |
static public function handleError($error_number, $error_string, $error_file = NULL, $error_line = NULL, $error_context = NULL) { |
| 813 |
if (self::$dynamic_constants && $error_number == E_NOTICE) { |
| 814 |
if (preg_match("#^Use of undefined constant (\w+) - assumed '\w+'\$#D", $error_string, $matches)) { |
| 815 |
define($matches[1], $matches[1]); |
| 816 |
return; |
| 817 |
} |
| 818 |
} |
| 819 |
|
| 820 |
$capturing = (bool) self::$captured_error_level; |
| 821 |
$level_match = (bool) (error_reporting() & $error_number); |
| 822 |
|
| 823 |
if (!$capturing && !$level_match) { |
| 824 |
return; |
| 825 |
} |
| 826 |
|
| 827 |
$doc_root = realpath($_SERVER['DOCUMENT_ROOT']); |
| 828 |
$doc_root .= (substr($doc_root, -1) != '/' && substr($doc_root, -1) != '\\') ? '/' : ''; |
| 829 |
|
| 830 |
$backtrace = self::backtrace(1); |
| 831 |
|
| 832 |
// Remove the reference to handleError |
| 833 |
$backtrace = preg_replace('#: fCore::handleError\(.*?\)$#', '', $backtrace); |
| 834 |
|
| 835 |
$error_string = preg_replace('# \[<a href=\'.*?</a>\]: #', ': ', $error_string); |
| 836 |
|
| 837 |
// This was added in 5.2 |
| 838 |
if (!defined('E_RECOVERABLE_ERROR')) { |
| 839 |
define('E_RECOVERABLE_ERROR', 4096); |
| 840 |
} |
| 841 |
|
| 842 |
// These were added in 5.3 |
| 843 |
if (!defined('E_DEPRECATED')) { |
| 844 |
define('E_DEPRECATED', 8192); |
| 845 |
} |
| 846 |
|
| 847 |
if (!defined('E_USER_DEPRECATED')) { |
| 848 |
define('E_USER_DEPRECATED', 16384); |
| 849 |
} |
| 850 |
|
| 851 |
switch ($error_number) { |
| 852 |
case E_WARNING: $type = self::compose('Warning'); |
| 853 |
break; |
| 854 |
case E_NOTICE: $type = self::compose('Notice'); |
| 855 |
break; |
| 856 |
case E_USER_ERROR: $type = self::compose('User Error'); |
| 857 |
break; |
| 858 |
case E_USER_WARNING: $type = self::compose('User Warning'); |
| 859 |
break; |
| 860 |
case E_USER_NOTICE: $type = self::compose('User Notice'); |
| 861 |
break; |
| 862 |
case E_STRICT: $type = self::compose('Strict'); |
| 863 |
break; |
| 864 |
case E_RECOVERABLE_ERROR: $type = self::compose('Recoverable Error'); |
| 865 |
break; |
| 866 |
case E_DEPRECATED: $type = self::compose('Deprecated'); |
| 867 |
break; |
| 868 |
case E_USER_DEPRECATED: $type = self::compose('User Deprecated'); |
| 869 |
break; |
| 870 |
case E_CORE_ERROR: $type = self::compose('Fatal Error'); |
| 871 |
break; |
| 872 |
case E_ERROR: $type = self::compose('Fatal Error'); |
| 873 |
break; |
| 874 |
} |
| 875 |
|
| 876 |
if ($capturing) { |
| 877 |
$type_to_capture = (bool) (self::$captured_error_types[self::$captured_error_level] & $error_number); |
| 878 |
$string_to_capture = !self::$captured_error_regex[self::$captured_error_level] || (self::$captured_error_regex[self::$captured_error_level] && preg_match(self::$captured_error_regex[self::$captured_error_level], $error_string)); |
| 879 |
if ($type_to_capture && $string_to_capture) { |
| 880 |
self::$captured_errors[self::$captured_error_level][] = array( |
| 881 |
'number' => $error_number, |
| 882 |
'type' => $type, |
| 883 |
'string' => $error_string, |
| 884 |
'file' => str_replace($doc_root, '{doc_root}/', $error_file), |
| 885 |
'line' => $error_line, |
| 886 |
'backtrace' => $backtrace, |
| 887 |
'context' => $error_context |
| 888 |
); |
| 889 |
return; |
| 890 |
} |
| 891 |
|
| 892 |
// If the old handler is not this method, then we must have been trying to match a regex and failed |
| 893 |
// so we pass the error on to the original handler to do its thing |
| 894 |
if (self::$captured_errors_previous_handler[self::$captured_error_level] != array('fCore', 'handleError')) { |
| 895 |
if (self::$captured_errors_previous_handler[self::$captured_error_level] === NULL) { |
| 896 |
return FALSE; |
| 897 |
} |
| 898 |
return call_user_func(self::$captured_errors_previous_handler[self::$captured_error_level], $error_number, $error_string, $error_file, $error_line, $error_context); |
| 899 |
|
| 900 |
// If we get here, this method is the error handler, but we don't want to actually report the error so we return |
| 901 |
} elseif (!$level_match) { |
| 902 |
return; |
| 903 |
} |
| 904 |
} |
| 905 |
|
| 906 |
$error = $type . "\n" . str_pad('', strlen($type), '-') . "\n" . $backtrace . "\n" . $error_string; |
| 907 |
|
| 908 |
$backtrace_lines = explode("\n", $backtrace); |
| 909 |
|
| 910 |
self::sendMessageToDestination('error', $error, end($backtrace_lines)); |
| 911 |
} |
| 912 |
|
| 913 |
/** |
| 914 |
* Handles an uncaught exception, creating the necessary context information, sending it to the specified destination and finally executing the closing callback |
| 915 |
* |
| 916 |
* @internal |
| 917 |
* |
| 918 |
* @param object $exception The uncaught exception to handle |
| 919 |
* @return void |
| 920 |
*/ |
| 921 |
static public function handleException($exception) { |
| 922 |
$message = ($exception->getMessage()) ? $exception->getMessage() : '{no message}'; |
| 923 |
if ($exception instanceof fException) { |
| 924 |
$trace = $exception->formatTrace(); |
| 925 |
} else { |
| 926 |
$trace = $exception->getTraceAsString(); |
| 927 |
} |
| 928 |
$code = ($exception->getCode()) ? ' (code ' . $exception->getCode() . ')' : ''; |
| 929 |
|
| 930 |
$info = $trace . "\n" . $message . $code; |
| 931 |
$headline = self::compose("Uncaught") . " " . get_class($exception); |
| 932 |
$info_block = $headline . "\n" . str_pad('', strlen($headline), '-') . "\n" . trim($info); |
| 933 |
|
| 934 |
$trace_lines = explode("\n", $trace); |
| 935 |
|
| 936 |
self::sendMessageToDestination('exception', $info_block, end($trace_lines)); |
| 937 |
|
| 938 |
if (self::$exception_handler_callback === NULL) { |
| 939 |
if (self::$exception_destination != 'html' && $exception instanceof fException) { |
| 940 |
$exception->printMessage(); |
| 941 |
} |
| 942 |
return; |
| 943 |
} |
| 944 |
|
| 945 |
try { |
| 946 |
self::call(self::$exception_handler_callback, self::$exception_handler_parameters); |
| 947 |
} catch (Throwable $e) { |
| 948 |
trigger_error( |
| 949 |
self::compose('An exception was thrown in the %s closing code callback', 'setExceptionHandling()'), E_USER_ERROR |
| 950 |
); |
| 951 |
} |
| 952 |
} |
| 953 |
|
| 954 |
/** |
| 955 |
* Registers a callback to handle debug messages instead of the default action of calling ::expose() on the message |
| 956 |
* |
| 957 |
* @param callback $callback A callback that accepts a single parameter, the string debug message to handle |
| 958 |
* @return void |
| 959 |
*/ |
| 960 |
static public function registerDebugCallback($callback) { |
| 961 |
self::$debug_callback = self::callback($callback); |
| 962 |
} |
| 963 |
|
| 964 |
/** |
| 965 |
* Resets the configuration of the class |
| 966 |
* |
| 967 |
* @internal |
| 968 |
* |
| 969 |
* @return void |
| 970 |
*/ |
| 971 |
static public function reset() { |
| 972 |
if (self::$handles_errors) { |
| 973 |
restore_error_handler(); |
| 974 |
} |
| 975 |
if (self::$handles_exceptions) { |
| 976 |
restore_exception_handler(); |
| 977 |
} |
| 978 |
|
| 979 |
if (is_array(self::$captured_errors)) { |
| 980 |
restore_error_handler(); |
| 981 |
} |
| 982 |
|
| 983 |
self::$captured_error_level = 0; |
| 984 |
self::$captured_error_regex = array(); |
| 985 |
self::$captured_error_types = array(); |
| 986 |
self::$captured_errors = array(); |
| 987 |
self::$captured_errors_previous_handler = array(); |
| 988 |
self::$context_shown = FALSE; |
| 989 |
self::$debug = NULL; |
| 990 |
self::$debug_callback = NULL; |
| 991 |
self::$dynamic_constants = FALSE; |
| 992 |
self::$error_destination = 'html'; |
| 993 |
self::$error_message_queue = array(); |
| 994 |
self::$exception_destination = 'html'; |
| 995 |
self::$exception_handler_callback = NULL; |
| 996 |
self::$exception_handler_parameters = array(); |
| 997 |
self::$exception_message = NULL; |
| 998 |
self::$handles_errors = FALSE; |
| 999 |
self::$handles_exceptions = FALSE; |
| 1000 |
self::$significant_error_lines = array(); |
| 1001 |
self::$show_context = TRUE; |
| 1002 |
self::$smtp_connection = NULL; |
| 1003 |
self::$smtp_from_email = NULL; |
| 1004 |
} |
| 1005 |
|
| 1006 |
/** |
| 1007 |
* Sends an email or writes a file with messages generated during the page execution |
| 1008 |
* |
| 1009 |
* This method prevents multiple emails from being sent or a log file from |
| 1010 |
* being written multiple times for one script execution. |
| 1011 |
* |
| 1012 |
* @internal |
| 1013 |
* |
| 1014 |
* @return void |
| 1015 |
*/ |
| 1016 |
static public function sendMessagesOnShutdown() { |
| 1017 |
$messages = array(); |
| 1018 |
|
| 1019 |
if (self::$error_message_queue) { |
| 1020 |
$message = join("\n\n", self::$error_message_queue); |
| 1021 |
$messages[self::$error_destination] = $message; |
| 1022 |
} |
| 1023 |
|
| 1024 |
if (self::$exception_message) { |
| 1025 |
if (isset($messages[self::$exception_destination])) { |
| 1026 |
$messages[self::$exception_destination] .= "\n\n"; |
| 1027 |
} else { |
| 1028 |
$messages[self::$exception_destination] = ''; |
| 1029 |
} |
| 1030 |
$messages[self::$exception_destination] .= self::$exception_message; |
| 1031 |
} |
| 1032 |
|
| 1033 |
$hash = md5(join('', self::$significant_error_lines), TRUE); |
| 1034 |
$hash = strtr(base64_encode($hash), '/', '-'); |
| 1035 |
$hash = substr(rtrim($hash, '='), 0, 8); |
| 1036 |
|
| 1037 |
$first_file_line = preg_replace( |
| 1038 |
'#^.*[/\\\\](.*)$#', '\1', reset(self::$significant_error_lines) |
| 1039 |
); |
| 1040 |
|
| 1041 |
$subject = self::compose('[%1$s] %2$s error(s) beginning at %3$s {%4$s}', isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : PHP_OS, count($messages), $first_file_line, $hash); |
| 1042 |
|
| 1043 |
foreach ($messages as $destination => $message) { |
| 1044 |
if (self::$show_context) { |
| 1045 |
$message .= "\n\n" . self::generateContext(); |
| 1046 |
} |
| 1047 |
|
| 1048 |
if (self::checkDestination($destination) == 'email') { |
| 1049 |
if (self::$smtp_connection) { |
| 1050 |
$email = new fEmail(); |
| 1051 |
foreach (explode(',', $destination) as $recipient) { |
| 1052 |
$email->addRecipient($recipient); |
| 1053 |
} |
| 1054 |
$email->setFromEmail(self::$smtp_from_email); |
| 1055 |
$email->setSubject($subject); |
| 1056 |
$email->setBody($message); |
| 1057 |
$email->send(self::$smtp_connection); |
| 1058 |
} else { |
| 1059 |
mail($destination, $subject, $message); |
| 1060 |
} |
| 1061 |
} else { |
| 1062 |
$handle = fopen($destination, 'a'); |
| 1063 |
fwrite($handle, $subject . "\n\n"); |
| 1064 |
fwrite($handle, $message . "\n\n"); |
| 1065 |
fclose($handle); |
| 1066 |
} |
| 1067 |
} |
| 1068 |
} |
| 1069 |
|
| 1070 |
/** |
| 1071 |
* Handles sending a message to a destination |
| 1072 |
* |
| 1073 |
* If the destination is an email address or file, the messages will be |
| 1074 |
* spooled up until the end of the script execution to prevent multiple |
| 1075 |
* emails from being sent or a log file being written to multiple times. |
| 1076 |
* |
| 1077 |
* @param string $type If the message is an error or an exception |
| 1078 |
* @param string $message The message to send to the destination |
| 1079 |
* @param string $significant_line The most significant line from an error or exception backtrace |
| 1080 |
* @return void |
| 1081 |
*/ |
| 1082 |
static private function sendMessageToDestination($type, $message, $significant_line) { |
| 1083 |
$destination = ($type == 'exception') ? self::$exception_destination : self::$error_destination; |
| 1084 |
|
| 1085 |
if ($destination == 'html') { |
| 1086 |
if (self::$show_context && !self::$context_shown) { |
| 1087 |
self::expose(self::generateContext()); |
| 1088 |
self::$context_shown = TRUE; |
| 1089 |
} |
| 1090 |
self::expose($message); |
| 1091 |
return; |
| 1092 |
} |
| 1093 |
|
| 1094 |
static $registered_function = FALSE; |
| 1095 |
if (!$registered_function) { |
| 1096 |
register_shutdown_function(self::callback(self::sendMessagesOnShutdown)); |
| 1097 |
$registered_function = TRUE; |
| 1098 |
} |
| 1099 |
|
| 1100 |
if ($type == 'error') { |
| 1101 |
self::$error_message_queue[] = $message; |
| 1102 |
} else { |
| 1103 |
self::$exception_message = $message; |
| 1104 |
} |
| 1105 |
|
| 1106 |
self::$significant_error_lines[] = $significant_line; |
| 1107 |
} |
| 1108 |
|
| 1109 |
/** |
| 1110 |
* Temporarily enables capturing error messages |
| 1111 |
* |
| 1112 |
* @param integer $types The error types to capture - this should be as specific as possible - defaults to all (E_ALL | E_STRICT) |
| 1113 |
* @param string $regex A PCRE regex to match against the error message |
| 1114 |
* @return void |
| 1115 |
*/ |
| 1116 |
static public function startErrorCapture($types = NULL, $regex = NULL) { |
| 1117 |
if ($types === NULL) { |
| 1118 |
$types = E_ALL | E_STRICT; |
| 1119 |
} |
| 1120 |
|
| 1121 |
self::$captured_error_level++; |
| 1122 |
|
| 1123 |
self::$captured_error_regex[self::$captured_error_level] = $regex; |
| 1124 |
self::$captured_error_types[self::$captured_error_level] = $types; |
| 1125 |
self::$captured_errors[self::$captured_error_level] = array(); |
| 1126 |
self::$captured_errors_previous_handler[self::$captured_error_level] = set_error_handler(self::callback(self::handleError)); |
| 1127 |
} |
| 1128 |
|
| 1129 |
/** |
| 1130 |
* Stops capturing error messages, returning all that have been captured |
| 1131 |
* |
| 1132 |
* @param string $regex A PCRE regex to filter messages by |
| 1133 |
* @return array The captured error messages |
| 1134 |
*/ |
| 1135 |
static public function stopErrorCapture($regex = NULL) { |
| 1136 |
$captures = self::$captured_errors[self::$captured_error_level]; |
| 1137 |
|
| 1138 |
self::$captured_error_level--; |
| 1139 |
|
| 1140 |
self::$captured_error_regex = array_slice(self::$captured_error_regex, 0, self::$captured_error_level, TRUE); |
| 1141 |
self::$captured_error_types = array_slice(self::$captured_error_types, 0, self::$captured_error_level, TRUE); |
| 1142 |
self::$captured_errors = array_slice(self::$captured_errors, 0, self::$captured_error_level, TRUE); |
| 1143 |
self::$captured_errors_previous_handler = array_slice(self::$captured_errors_previous_handler, 0, self::$captured_error_level, TRUE); |
| 1144 |
|
| 1145 |
restore_error_handler(); |
| 1146 |
|
| 1147 |
if ($regex) { |
| 1148 |
$new_captures = array(); |
| 1149 |
foreach ($captures as $capture) { |
| 1150 |
if (!preg_match($regex, $capture['string'])) { |
| 1151 |
continue; |
| 1152 |
} |
| 1153 |
$new_captures[] = $capture; |
| 1154 |
} |
| 1155 |
$captures = $new_captures; |
| 1156 |
} |
| 1157 |
|
| 1158 |
return $captures; |
| 1159 |
} |
| 1160 |
|
| 1161 |
/** |
| 1162 |
* Forces use as a static class |
| 1163 |
* |
| 1164 |
* @return fCore |
| 1165 |
*/ |
| 1166 |
private function __construct() { |
| 1167 |
|
| 1168 |
} |
| 1169 |
|
| 1170 |
} |
| 1171 |
|
| 1172 |
/** |
| 1173 |
* Copyright (c) 2007-2011 Will Bond <will@flourishlib.com>, others |
| 1174 |
* |
| 1175 |
* Permission is hereby granted, free of charge, to any person obtaining a copy |
| 1176 |
* of this software and associated documentation files (the "Software"), to deal |
| 1177 |
* in the Software without restriction, including without limitation the rights |
| 1178 |
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 1179 |
* copies of the Software, and to permit persons to whom the Software is |
| 1180 |
* furnished to do so, subject to the following conditions: |
| 1181 |
* |
| 1182 |
* The above copyright notice and this permission notice shall be included in |
| 1183 |
* all copies or substantial portions of the Software. |
| 1184 |
* |
| 1185 |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 1186 |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 1187 |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 1188 |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 1189 |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 1190 |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| 1191 |
* THE SOFTWARE. |
| 1192 |
*/ |