1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10: 11:
12:
13:
14:
15: 16: 17: 18: 19: 20: 21: 22: 23: 24:
25: final class NDebugger
26: {
27:
28: public static $productionMode;
29:
30:
31: public static $consoleMode;
32:
33:
34: public static $time;
35:
36:
37: private static $ajaxDetected;
38:
39:
40: public static $source;
41:
42:
43: public static $editor = 'editor://open/?file=%file&line=%line';
44:
45:
46:
47:
48: public static $maxDepth = 3;
49:
50:
51: public static $maxLen = 150;
52:
53:
54: public static $showLocation = FALSE;
55:
56:
57:
58:
59: const DEVELOPMENT = FALSE,
60: PRODUCTION = TRUE,
61: DETECT = NULL;
62:
63:
64: public static $blueScreen;
65:
66:
67: public static $strictMode = FALSE;
68:
69:
70: public static $scream = FALSE;
71:
72:
73: public static $onFatalError = array();
74:
75:
76: private static $enabled = FALSE;
77:
78:
79: private static $lastError = FALSE;
80:
81:
82:
83:
84: public static $logger;
85:
86:
87: public static $fireLogger;
88:
89:
90: public static $logDirectory;
91:
92:
93: public static $email;
94:
95:
96: public static $mailer;
97:
98:
99: public static $emailSnooze;
100:
101:
102:
103:
104: public static $bar;
105:
106:
107: private static $errorPanel;
108:
109:
110: private static $dumpPanel;
111:
112:
113:
114:
115: const DEBUG = 'debug',
116: INFO = 'info',
117: WARNING = 'warning',
118: ERROR = 'error',
119: CRITICAL = 'critical';
120:
121:
122:
123: 124: 125:
126: final public function __construct()
127: {
128: throw new NStaticClassException;
129: }
130:
131:
132:
133: 134: 135: 136:
137: public static function _init()
138: {
139: self::$time = microtime(TRUE);
140: self::$consoleMode = PHP_SAPI === 'cli';
141: self::$productionMode = self::DETECT;
142: if (self::$consoleMode) {
143: self::$source = empty($_SERVER['argv']) ? 'cli' : 'cli: ' . implode(' ', $_SERVER['argv']);
144: } else {
145: self::$ajaxDetected = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest';
146: if (isset($_SERVER['REQUEST_URI'])) {
147: self::$source = (isset($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'off') ? 'https://' : 'http://')
148: . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : ''))
149: . $_SERVER['REQUEST_URI'];
150: }
151: }
152:
153: self::$logger = new NLogger;
154: self::$logDirectory = & self::$logger->directory;
155: self::$email = & self::$logger->email;
156: self::$mailer = & self::$logger->mailer;
157: self::$emailSnooze = & NLogger::$emailSnooze;
158:
159: self::$fireLogger = new NFireLogger;
160:
161: self::$blueScreen = new NDebugBlueScreen;
162: self::$blueScreen->addPanel(create_function('$e', '
163: if ($e instanceof NTemplateException) {
164: return array(
165: \'tab\' => \'Template\',
166: \'panel\' => \'<p><b>File:</b> \' . NDebugHelpers::editorLink($e->sourceFile, $e->sourceLine)
167: . \' <b>Line:</b> \' . ($e->sourceLine ? $e->sourceLine : \'n/a\') . \'</p>\'
168: . ($e->sourceLine ? \'<pre>\' . NDebugBlueScreen::highlightFile($e->sourceFile, $e->sourceLine) . \'</pre>\' : \'\')
169: );
170: }
171: '));
172:
173: self::$bar = new NDebugBar;
174: self::$bar->addPanel(new NDefaultBarPanel('time'));
175: self::$bar->addPanel(new NDefaultBarPanel('memory'));
176: self::$bar->addPanel(self::$errorPanel = new NDefaultBarPanel('errors'));
177: self::$bar->addPanel(self::$dumpPanel = new NDefaultBarPanel('dumps'));
178: }
179:
180:
181:
182:
183:
184:
185:
186: 187: 188: 189: 190: 191: 192:
193: public static function enable($mode = NULL, $logDirectory = NULL, $email = NULL)
194: {
195: error_reporting(E_ALL | E_STRICT);
196:
197:
198: if (is_bool($mode)) {
199: self::$productionMode = $mode;
200:
201: } elseif (is_string($mode)) {
202: $mode = preg_split('#[,\s]+#', "$mode 127.0.0.1 ::1");
203: }
204:
205: if (is_array($mode)) {
206: self::$productionMode = !isset($_SERVER['REMOTE_ADDR']) || !in_array($_SERVER['REMOTE_ADDR'], $mode, TRUE);
207: }
208:
209: if (self::$productionMode === self::DETECT) {
210: if (isset($_SERVER['SERVER_ADDR']) || isset($_SERVER['LOCAL_ADDR'])) {
211: $addrs = array();
212: if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
213: $addrs = preg_split('#,\s*#', $_SERVER['HTTP_X_FORWARDED_FOR']);
214: }
215: if (isset($_SERVER['REMOTE_ADDR'])) {
216: $addrs[] = $_SERVER['REMOTE_ADDR'];
217: }
218: $addrs[] = isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : $_SERVER['LOCAL_ADDR'];
219: self::$productionMode = FALSE;
220: foreach ($addrs as $addr) {
221: $oct = explode('.', $addr);
222: if ($addr !== '::1' && (count($oct) !== 4 || ($oct[0] !== '10' && $oct[0] !== '127' && ($oct[0] !== '172' || $oct[1] < 16 || $oct[1] > 31)
223: && ($oct[0] !== '169' || $oct[1] !== '254') && ($oct[0] !== '192' || $oct[1] !== '168')))
224: ) {
225: self::$productionMode = TRUE;
226: break;
227: }
228: }
229:
230: } else {
231: self::$productionMode = !self::$consoleMode;
232: }
233: }
234:
235:
236: if (is_string($logDirectory)) {
237: self::$logDirectory = realpath($logDirectory);
238: if (self::$logDirectory === FALSE) {
239: throw new DirectoryNotFoundException("Directory '$logDirectory' is not found.");
240: }
241: } elseif ($logDirectory === FALSE) {
242: self::$logDirectory = FALSE;
243:
244: } elseif (self::$logDirectory === NULL) {
245: self::$logDirectory = defined('APP_DIR') ? APP_DIR . '/../log' : getcwd() . '/log';
246: }
247: if (self::$logDirectory) {
248: ini_set('error_log', self::$logDirectory . '/php_error.log');
249: }
250:
251:
252: if (function_exists('ini_set')) {
253: ini_set('display_errors', !self::$productionMode);
254: ini_set('html_errors', FALSE);
255: ini_set('log_errors', FALSE);
256:
257: } elseif (ini_get('display_errors') != !self::$productionMode && ini_get('display_errors') !== (self::$productionMode ? 'stderr' : 'stdout')) {
258: throw new NotSupportedException('Function ini_set() must be enabled.');
259: }
260:
261: if ($email) {
262: if (!is_string($email)) {
263: throw new InvalidArgumentException('Email address must be a string.');
264: }
265: self::$email = $email;
266: }
267:
268: if (!defined('E_DEPRECATED')) {
269: define('E_DEPRECATED', 8192);
270: }
271:
272: if (!defined('E_USER_DEPRECATED')) {
273: define('E_USER_DEPRECATED', 16384);
274: }
275:
276: if (!self::$enabled) {
277: register_shutdown_function(array(__CLASS__, '_shutdownHandler'));
278: set_exception_handler(array(__CLASS__, '_exceptionHandler'));
279: set_error_handler(array(__CLASS__, '_errorHandler'));
280: self::$enabled = TRUE;
281: }
282: }
283:
284:
285:
286: 287: 288: 289:
290: public static function isEnabled()
291: {
292: return self::$enabled;
293: }
294:
295:
296:
297: 298: 299: 300: 301: 302:
303: public static function log($message, $priority = self::INFO)
304: {
305: if (self::$logDirectory === FALSE) {
306: return;
307:
308: } elseif (!self::$logDirectory) {
309: throw new InvalidStateException('Logging directory is not specified in NDebugger::$logDirectory.');
310: }
311:
312: if ($message instanceof Exception) {
313: $exception = $message;
314: $message = "PHP Fatal error: "
315: . ($message instanceof FatalErrorException
316: ? $exception->getMessage()
317: : "Uncaught exception " . get_class($exception) . " with message '" . $exception->getMessage() . "'")
318: . " in " . $exception->getFile() . ":" . $exception->getLine();
319:
320: $hash = md5($exception . (method_exists($exception, 'getPrevious') ? $exception->getPrevious() : (isset($exception->previous) ? $exception->previous : '')));
321: $exceptionFilename = "exception " . @date('Y-m-d H-i-s') . " $hash.html";
322: foreach (new DirectoryIterator(self::$logDirectory) as $entry) {
323: if (strpos($entry, $hash)) {
324: $exceptionFilename = NULL; break;
325: }
326: }
327: }
328:
329: self::$logger->log(array(
330: @date('[Y-m-d H-i-s]'),
331: $message,
332: self::$source ? ' @ ' . self::$source : NULL,
333: !empty($exceptionFilename) ? ' @@ ' . $exceptionFilename : NULL
334: ), $priority);
335:
336: if (!empty($exceptionFilename) && $logHandle = @fopen(self::$logDirectory . '/'. $exceptionFilename, 'w')) {
337: ob_start();
338: ob_start(create_function('$buffer', 'extract(NCFix::$vars['.NCFix::uses(array('logHandle'=>$logHandle)).'], EXTR_REFS); fwrite($logHandle, $buffer); '), 1);
339: self::$blueScreen->render($exception);
340: ob_end_flush();
341: ob_end_clean();
342: fclose($logHandle);
343: }
344: }
345:
346:
347:
348: 349: 350: 351: 352:
353: public static function _shutdownHandler()
354: {
355: if (!self::$enabled) {
356: return;
357: }
358:
359:
360: static $types = array(
361: E_ERROR => 1,
362: E_CORE_ERROR => 1,
363: E_COMPILE_ERROR => 1,
364: E_PARSE => 1,
365: );
366: $error = error_get_last();
367: if (isset($types[$error['type']])) {
368: self::_exceptionHandler(new FatalErrorException($error['message'], 0, $error['type'], $error['file'], $error['line'], NULL));
369: }
370:
371:
372: if (self::$bar && !self::$productionMode && self::isHtmlMode()) {
373: self::$bar->render();
374: }
375: }
376:
377:
378:
379: 380: 381: 382: 383: 384:
385: public static function _exceptionHandler(Exception $exception)
386: {
387: if (!headers_sent()) {
388: header('HTTP/1.1 500 Internal Server Error');
389: }
390:
391: try {
392: if (self::$productionMode) {
393: self::log($exception, self::ERROR);
394:
395: if (self::$consoleMode) {
396: echo "ERROR: the server encountered an internal error and was unable to complete your request.\n";
397:
398: } elseif (self::isHtmlMode()) {
399: require dirname(__FILE__) . '/templates/error.phtml';
400: }
401:
402: } else {
403: if (self::$consoleMode) {
404: echo "$exception\n";
405:
406: } elseif (self::isHtmlMode()) {
407: self::$blueScreen->render($exception);
408: if (self::$bar) {
409: self::$bar->render();
410: }
411:
412: } elseif (!self::fireLog($exception, self::ERROR)) {
413: self::log($exception);
414: }
415: }
416:
417: foreach (self::$onFatalError as $handler) {
418: call_user_func($handler, $exception);
419: }
420: } catch (Exception $e) {
421: echo "\nNDebug FATAL ERROR: thrown ", get_class($e), ': ', $e->getMessage(),
422: "\nwhile processing ", get_class($exception), ': ', $exception->getMessage(), "\n";
423: }
424: self::$enabled = FALSE;
425: exit(255);
426: }
427:
428:
429:
430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440:
441: public static function _errorHandler($severity, $message, $file, $line, $context)
442: {
443: if (self::$scream) {
444: error_reporting(E_ALL | E_STRICT);
445: }
446:
447: if (self::$lastError !== FALSE && ($severity & error_reporting()) === $severity) {
448: self::$lastError = new ErrorException($message, 0, $severity, $file, $line);
449: return NULL;
450: }
451:
452: if ($severity === E_RECOVERABLE_ERROR || $severity === E_USER_ERROR) {
453: throw new FatalErrorException($message, 0, $severity, $file, $line, $context);
454:
455: } elseif (($severity & error_reporting()) !== $severity) {
456: return FALSE;
457:
458: } elseif (self::$strictMode && !self::$productionMode) {
459: self::_exceptionHandler(new FatalErrorException($message, 0, $severity, $file, $line, $context));
460: }
461:
462: static $types = array(
463: E_WARNING => 'Warning',
464: E_COMPILE_WARNING => 'Warning',
465: E_USER_WARNING => 'Warning',
466: E_NOTICE => 'Notice',
467: E_USER_NOTICE => 'Notice',
468: E_STRICT => 'Strict standards',
469: E_DEPRECATED => 'Deprecated',
470: E_USER_DEPRECATED => 'Deprecated',
471: );
472:
473: $message = 'PHP ' . (isset($types[$severity]) ? $types[$severity] : 'Unknown error') . ": $message";
474: $count = & self::$errorPanel->data["$message|$file|$line"];
475:
476: if ($count++) {
477: return NULL;
478:
479: } elseif (self::$productionMode) {
480: self::log("$message in $file:$line", self::ERROR);
481: return NULL;
482:
483: } else {
484: $ok = self::fireLog(new ErrorException($message, 0, $severity, $file, $line), self::WARNING);
485: return !self::isHtmlMode() || (!self::$bar && !$ok) ? FALSE : NULL;
486: }
487:
488: return FALSE;
489: }
490:
491:
492:
493: 494: 495: 496: 497:
498: public static function toStringException(Exception $exception)
499: {
500: if (self::$enabled) {
501: self::_exceptionHandler($exception);
502: } else {
503: trigger_error($exception->getMessage(), E_USER_ERROR);
504: }
505: }
506:
507:
508:
509: 510: 511: 512:
513: public static function tryError()
514: {
515: if (!self::$enabled && self::$lastError === FALSE) {
516: set_error_handler(array(__CLASS__, '_errorHandler'));
517: }
518: self::$lastError = NULL;
519: }
520:
521:
522:
523: 524: 525: 526: 527:
528: public static function catchError(& $error)
529: {
530: if (!self::$enabled && self::$lastError !== FALSE) {
531: restore_error_handler();
532: }
533: $error = self::$lastError;
534: self::$lastError = FALSE;
535: return (bool) $error;
536: }
537:
538:
539:
540:
541:
542:
543:
544: 545: 546: 547: 548: 549:
550: public static function dump($var, $return = FALSE)
551: {
552: if (!$return && self::$productionMode) {
553: return $var;
554: }
555:
556: $output = "<pre class=\"nette-dump\">" . NDebugHelpers::htmlDump($var) . "</pre>\n";
557:
558: if (!$return) {
559: $trace = debug_backtrace();
560: $i = !isset($trace[1]['class']) && isset($trace[1]['function']) && $trace[1]['function'] === 'dump' ? 1 : 0;
561: if (isset($trace[$i]['file'], $trace[$i]['line']) && is_file($trace[$i]['file'])) {
562: $lines = file($trace[$i]['file']);
563: preg_match('#dump\((.*)\)#', $lines[$trace[$i]['line'] - 1], $m);
564: $output = substr_replace(
565: $output,
566: ' title="' . htmlspecialchars((isset($m[0]) ? "$m[0] \n" : '') . "in file {$trace[$i]['file']} on line {$trace[$i]['line']}") . '"',
567: 4, 0);
568:
569: if (self::$showLocation) {
570: $output = substr_replace(
571: $output,
572: ' <small>in ' . NDebugHelpers::editorLink($trace[$i]['file'], $trace[$i]['line']) . ":{$trace[$i]['line']}</small>",
573: -8, 0);
574: }
575: }
576: }
577:
578: if (self::$consoleMode) {
579: $output = htmlspecialchars_decode(strip_tags($output), ENT_NOQUOTES);
580: }
581:
582: if ($return) {
583: return $output;
584:
585: } else {
586: echo $output;
587: return $var;
588: }
589: }
590:
591:
592:
593: 594: 595: 596: 597:
598: public static function timer($name = NULL)
599: {
600: static $time = array();
601: $now = microtime(TRUE);
602: $delta = isset($time[$name]) ? $now - $time[$name] : 0;
603: $time[$name] = $now;
604: return $delta;
605: }
606:
607:
608:
609: 610: 611: 612: 613: 614:
615: public static function barDump($var, $title = NULL)
616: {
617: if (!self::$productionMode) {
618: $dump = array();
619: foreach ((is_array($var) ? $var : array('' => $var)) as $key => $val) {
620: $dump[$key] = NDebugHelpers::clickableDump($val);
621: }
622: self::$dumpPanel->data[] = array('title' => $title, 'dump' => $dump);
623: }
624: return $var;
625: }
626:
627:
628:
629: 630: 631: 632: 633:
634: public static function fireLog($message)
635: {
636: if (!self::$productionMode) {
637: return self::$fireLogger->log($message);
638: }
639: }
640:
641:
642:
643: private static function isHtmlMode()
644: {
645: return !self::$ajaxDetected && !self::$consoleMode
646: && !preg_match('#^Content-Type: (?!text/html)#im', implode("\n", headers_list()));
647: }
648:
649:
650:
651:
652: public static function addPanel(IBarPanel $panel, $id = NULL)
653: {
654: self::$bar->addPanel($panel, $id);
655: }
656:
657: }
658: