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