PluginProbe
Postie / trunk
Postie vtrunk
1.9.80 1.9.79 1.9.78 1.9.77 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.10 1.7.11 1.7.12 1.7.13 1.7.14 1.7.15 1.7.16 1.7.17 1.7.18 1.7.2 1.7.20 All 239 releases
postie / lib / fCore.php

fCore.php in Postie trunk, at lib/fCore.php

1,192 lines 47.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 */