1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace Nette;
13:
14: use Nette,
15: Nette\Environment;
16:
17:
18:
19: 20: 21: 22: 23: 24: 25: 26: 27:
28: final class Debug
29: {
30:
31: public static $productionMode;
32:
33:
34: public static $consoleMode;
35:
36:
37: public static $time;
38:
39:
40: private static $firebugDetected;
41:
42:
43: private static $ajaxDetected;
44:
45:
46: public static $source;
47:
48:
49:
50:
51: public static $maxDepth = 3;
52:
53:
54: public static $maxLen = 150;
55:
56:
57: public static $showLocation = FALSE;
58:
59:
60:
61:
62: const DEVELOPMENT = FALSE,
63: PRODUCTION = TRUE,
64: DETECT = NULL;
65:
66:
67: public static $strictMode = FALSE; 68:
69:
70: public static $scream = FALSE;
71:
72:
73: public static $onFatalError = array();
74:
75:
76: public static $logDirectory;
77:
78:
79: public static $email;
80:
81:
82: public static $mailer = array(__CLASS__, 'defaultMailer');
83:
84:
85: public static $emailSnooze = 172800;
86:
87:
88: public static $editor = 'editor://open/?file=%file&line=%line';
89:
90:
91: private static $enabled = FALSE;
92:
93:
94: private static $lastError = FALSE;
95:
96:
97:
98:
99: public static $showBar = TRUE;
100:
101:
102: private static $panels = array();
103:
104:
105: private static $errors;
106:
107:
108:
109:
110: const DEBUG = 'debug',
111: INFO = 'info',
112: WARNING = 'warning',
113: ERROR = 'error',
114: CRITICAL = 'critical';
115:
116:
117:
118: 119: 120:
121: final public function __construct()
122: {
123: throw new \LogicException("Cannot instantiate static class " . get_class($this));
124: }
125:
126:
127:
128: 129: 130: 131:
132: public static function _init()
133: {
134: self::$time = microtime(TRUE);
135: self::$consoleMode = PHP_SAPI === 'cli';
136: self::$productionMode = self::DETECT;
137: if (self::$consoleMode) {
138: self::$source = empty($_SERVER['argv']) ? 'cli' : 'cli: ' . implode(' ', $_SERVER['argv']);
139: } else {
140: self::$firebugDetected = isset($_SERVER['HTTP_X_FIRELOGGER']);
141: self::$ajaxDetected = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest';
142: if (isset($_SERVER['REQUEST_URI'])) {
143: self::$source = (isset($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'off') ? 'https://' : 'http://')
144: . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : ''))
145: . $_SERVER['REQUEST_URI'];
146: }
147: }
148:
149: $tab = array('Nette\DebugHelpers', 'renderTab'); $panel = array('Nette\DebugHelpers', 'renderPanel');
150: self::addPanel(new DebugPanel('time', $tab, $panel));
151: self::addPanel(new DebugPanel('memory', $tab, $panel));
152: self::addPanel($tmp = new DebugPanel('errors', $tab, $panel)); $tmp->data = & self::$errors;
153: self::addPanel(new DebugPanel('dumps', $tab, $panel));
154: }
155:
156:
157:
158:
159:
160:
161:
162: 163: 164: 165: 166: 167: 168:
169: public static function enable($mode = NULL, $logDirectory = NULL, $email = NULL)
170: {
171: error_reporting(E_ALL | E_STRICT);
172:
173: 174: if (is_bool($mode)) {
175: self::$productionMode = $mode;
176:
177: } elseif (is_string($mode)) { 178: $mode = preg_split('#[,\s]+#', "$mode 127.0.0.1 ::1");
179: }
180:
181: if (is_array($mode)) { 182: self::$productionMode = !isset($_SERVER['REMOTE_ADDR']) || !in_array($_SERVER['REMOTE_ADDR'], $mode, TRUE);
183: }
184:
185: if (self::$productionMode === self::DETECT) {
186: if (class_exists('Nette\Environment')) {
187: self::$productionMode = Environment::isProduction();
188:
189: } elseif (isset($_SERVER['SERVER_ADDR']) || isset($_SERVER['LOCAL_ADDR'])) { 190: $addrs = array();
191: if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { 192: $addrs = preg_split('#,\s*#', $_SERVER['HTTP_X_FORWARDED_FOR']);
193: }
194: if (isset($_SERVER['REMOTE_ADDR'])) {
195: $addrs[] = $_SERVER['REMOTE_ADDR'];
196: }
197: $addrs[] = isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : $_SERVER['LOCAL_ADDR'];
198: self::$productionMode = FALSE;
199: foreach ($addrs as $addr) {
200: $oct = explode('.', $addr);
201: if ($addr !== '::1' && (count($oct) !== 4 || ($oct[0] !== '10' && $oct[0] !== '127' && ($oct[0] !== '172' || $oct[1] < 16 || $oct[1] > 31)
202: && ($oct[0] !== '169' || $oct[1] !== '254') && ($oct[0] !== '192' || $oct[1] !== '168')))
203: ) {
204: self::$productionMode = TRUE;
205: break;
206: }
207: }
208:
209: } else {
210: self::$productionMode = !self::$consoleMode;
211: }
212: }
213:
214: 215: if (is_string($logDirectory)) {
216: self::$logDirectory = realpath($logDirectory);
217: if (self::$logDirectory === FALSE) {
218: throw new \DirectoryNotFoundException("Directory '$logDirectory' is not found.");
219: }
220: } elseif ($logDirectory === FALSE) {
221: self::$logDirectory = FALSE;
222:
223: } else {
224: self::$logDirectory = defined('APP_DIR') ? APP_DIR . '/../log' : getcwd() . '/log';
225: }
226: if (self::$logDirectory) {
227: ini_set('error_log', self::$logDirectory . '/php_error.log');
228: }
229:
230: 231: if (function_exists('ini_set')) {
232: ini_set('display_errors', !self::$productionMode); 233: ini_set('html_errors', FALSE);
234: ini_set('log_errors', FALSE);
235:
236: } elseif (ini_get('display_errors') != !self::$productionMode && ini_get('display_errors') !== (self::$productionMode ? 'stderr' : 'stdout')) { 237: throw new \NotSupportedException('Function ini_set() must be enabled.');
238: }
239:
240: if ($email) {
241: if (!is_string($email)) {
242: throw new \InvalidArgumentException('Email address must be a string.');
243: }
244: self::$email = $email;
245: }
246:
247: if (!defined('E_DEPRECATED')) {
248: define('E_DEPRECATED', 8192);
249: }
250:
251: if (!defined('E_USER_DEPRECATED')) {
252: define('E_USER_DEPRECATED', 16384);
253: }
254:
255: if (!self::$enabled) {
256: register_shutdown_function(array(__CLASS__, '_shutdownHandler'));
257: set_exception_handler(array(__CLASS__, '_exceptionHandler'));
258: set_error_handler(array(__CLASS__, '_errorHandler'));
259: self::$enabled = TRUE;
260: }
261: }
262:
263:
264:
265: 266: 267: 268:
269: public static function isEnabled()
270: {
271: return self::$enabled;
272: }
273:
274:
275:
276: 277: 278: 279: 280: 281:
282: public static function log($message, $priority = self::INFO)
283: {
284: if (self::$logDirectory === FALSE) {
285: return;
286:
287: } elseif (!self::$logDirectory) {
288: throw new \InvalidStateException('Logging directory is not specified in Nette\Debug::$logDirectory.');
289:
290: } elseif (!is_dir(self::$logDirectory)) {
291: throw new \DirectoryNotFoundException("Directory '" . self::$logDirectory . "' is not found or is not directory.");
292: }
293:
294: if ($message instanceof \Exception) {
295: $exception = $message;
296: $message = "PHP Fatal error: "
297: . ($message instanceof \FatalErrorException ? $exception->getMessage() : "Uncaught exception " . get_class($exception) . " with message '" . $exception->getMessage() . "'")
298: . " in " . $exception->getFile() . ":" . $exception->getLine();
299:
300: $hash = md5($exception );
301: $exceptionFilename = "exception " . @date('Y-m-d H-i-s') . " $hash.html";
302: foreach (new \DirectoryIterator(self::$logDirectory) as $entry) {
303: if (strpos($entry, $hash)) {
304: $exceptionFilename = NULL; break;
305: }
306: }
307: }
308:
309: error_log(
310: @date('[Y-m-d H-i-s] ') . trim($message) .
311: (self::$source ? ' @ ' . self::$source : '') .
312: (!empty($exceptionFilename) ? ' @@ ' . $exceptionFilename : '') . PHP_EOL,
313: 3, self::$logDirectory . '/' . strtolower($priority) . '.log'
314: );
315:
316: if (($priority === self::ERROR || $priority === self::CRITICAL) && self::$email
317: && @filemtime(self::$logDirectory . '/email-sent') + self::$emailSnooze < time() 318: && @file_put_contents(self::$logDirectory . '/email-sent', 'sent')) { 319: call_user_func(self::$mailer, $message);
320: }
321:
322: if (!empty($exceptionFilename) && $logHandle = @fopen(self::$logDirectory . '/'. $exceptionFilename, 'w')) {
323: ob_start(); 324: ob_start(function($buffer) use ($logHandle) { fwrite($logHandle, $buffer); }, 1);
325: DebugHelpers::renderBlueScreen($exception);
326: ob_end_flush();
327: ob_end_clean();
328: fclose($logHandle);
329: }
330: }
331:
332:
333:
334: 335: 336: 337: 338:
339: public static function _shutdownHandler()
340: {
341: 342: static $types = array(
343: E_ERROR => 1,
344: E_CORE_ERROR => 1,
345: E_COMPILE_ERROR => 1,
346: E_PARSE => 1,
347: );
348: $error = error_get_last();
349: if (isset($types[$error['type']])) {
350: self::_exceptionHandler(new \FatalErrorException($error['message'], 0, $error['type'], $error['file'], $error['line'], NULL));
351: return;
352: }
353:
354: 355: if (self::$showBar && !self::$productionMode && !self::$ajaxDetected && !self::$consoleMode
356: && (!preg_match('#^Content-Type: (?!text/html)#im', implode("\n", headers_list())))) {
357: DebugHelpers::renderDebugBar(self::$panels);
358: }
359: }
360:
361:
362:
363: 364: 365: 366: 367: 368:
369: public static function _exceptionHandler(\Exception $exception)
370: {
371: if (!headers_sent()) { 372: header('HTTP/1.1 500 Internal Server Error');
373: }
374:
375: $htmlMode = !self::$ajaxDetected && !preg_match('#^Content-Type: (?!text/html)#im', implode("\n", headers_list()));
376:
377: try {
378: if (self::$productionMode) {
379: self::log($exception, self::ERROR);
380:
381: if (self::$consoleMode) {
382: echo "ERROR: the server encountered an internal error and was unable to complete your request.\n";
383:
384: } elseif ($htmlMode) {
385: echo "<!DOCTYPE html><meta http-equiv='Content-Type' content='text/html; charset=utf-8'><meta name=robots content=noindex><meta name=generator content='Nette Framework'>\n\n";
386: echo "<style>body{color:#333;background:white;width:500px;margin:100px auto}h1{font:bold 47px/1.5 sans-serif;margin:.6em 0}p{font:21px/1.5 Georgia,serif;margin:1.5em 0}small{font-size:70%;color:gray}</style>\n\n";
387: echo "<title>Server Error</title>\n\n<h1>Server Error</h1>\n\n<p>We're sorry! The server encountered an internal error and was unable to complete your request. Please try again later.</p>\n\n<p><small>error 500</small></p>";
388: }
389:
390: } else {
391: if (self::$consoleMode) { 392: echo "$exception\n";
393:
394: } elseif ($htmlMode) { 395: DebugHelpers::renderBlueScreen($exception);
396:
397: } elseif (!self::fireLog($exception, self::ERROR)) { 398: self::log($exception);
399: }
400: }
401:
402: foreach (self::$onFatalError as $handler) {
403: call_user_func($handler, $exception);
404: }
405: } catch (\Exception $e) {
406: echo "\nNette\\Debug FATAL ERROR: thrown ", get_class($e), ': ', $e->getMessage(), "\nwhile processing ", get_class($exception), ': ', $exception->getMessage(), "\n";
407: exit;
408: }
409: }
410:
411:
412:
413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423:
424: public static function _errorHandler($severity, $message, $file, $line, $context)
425: {
426: if (self::$scream) {
427: error_reporting(E_ALL | E_STRICT);
428: }
429:
430: if (self::$lastError !== FALSE && ($severity & error_reporting()) === $severity) { 431: self::$lastError = new \ErrorException($message, 0, $severity, $file, $line);
432: return NULL;
433: }
434:
435: if ($severity === E_RECOVERABLE_ERROR || $severity === E_USER_ERROR) {
436: throw new \FatalErrorException($message, 0, $severity, $file, $line, $context);
437:
438: } elseif (($severity & error_reporting()) !== $severity) {
439: return FALSE; 440:
441: } elseif (self::$strictMode && !self::$productionMode) {
442: self::_exceptionHandler(new \FatalErrorException($message, 0, $severity, $file, $line, $context));
443: exit;
444: }
445:
446: static $types = array(
447: E_WARNING => 'Warning',
448: E_COMPILE_WARNING => 'Warning', 449: E_USER_WARNING => 'Warning',
450: E_NOTICE => 'Notice',
451: E_USER_NOTICE => 'Notice',
452: E_STRICT => 'Strict standards',
453: E_DEPRECATED => 'Deprecated',
454: E_USER_DEPRECATED => 'Deprecated',
455: );
456:
457: $message = 'PHP ' . (isset($types[$severity]) ? $types[$severity] : 'Unknown error') . ": $message";
458: $count = & self::$errors["$message|$file|$line"];
459:
460: if ($count++) { 461: return NULL;
462:
463: } elseif (self::$productionMode) {
464: self::log("$message in $file:$line", self::ERROR);
465: return NULL;
466:
467: } else {
468: $ok = self::fireLog(new \ErrorException($message, 0, $severity, $file, $line), self::WARNING);
469: return self::$consoleMode || (!self::$showBar && !$ok) ? FALSE : NULL;
470: }
471:
472: return FALSE; 473: }
474:
475:
476:
477:
478: public static function processException(\Exception $exception)
479: {
480: trigger_error(__METHOD__ . '() is deprecated; use ' . __CLASS__ . '::log($exception, Debug::ERROR) instead.', E_USER_WARNING);
481: self::log($exception, self::ERROR);
482: }
483:
484:
485:
486: 487: 488: 489: 490:
491: public static function toStringException(\Exception $exception)
492: {
493: if (self::$enabled) {
494: self::_exceptionHandler($exception);
495: } else {
496: trigger_error($exception->getMessage(), E_USER_ERROR);
497: }
498: exit;
499: }
500:
501:
502:
503: 504: 505: 506:
507: public static function tryError()
508: {
509: if (!self::$enabled && self::$lastError === FALSE) {
510: set_error_handler(array(__CLASS__, '_errorHandler'));
511: }
512: self::$lastError = NULL;
513: }
514:
515:
516:
517: 518: 519: 520: 521:
522: public static function catchError(& $error)
523: {
524: if (!self::$enabled && self::$lastError !== FALSE) {
525: restore_error_handler();
526: }
527: $error = self::$lastError;
528: self::$lastError = FALSE;
529: return (bool) $error;
530: }
531:
532:
533:
534: 535: 536: 537: 538:
539: private static function defaultMailer($message)
540: {
541: $host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] :
542: (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '');
543:
544: $parts = str_replace(
545: array("\r\n", "\n"),
546: array("\n", PHP_EOL),
547: array(
548: 'headers' => "From: noreply@$host\nX-Mailer: Nette Framework\n",
549: 'subject' => "PHP: An error occurred on the server $host",
550: 'body' => "[" . @date('Y-m-d H:i:s') . "] $message", 551: )
552: );
553:
554: mail(self::$email, $parts['subject'], $parts['body'], $parts['headers']);
555: }
556:
557:
558:
559:
560:
561:
562:
563: 564: 565: 566: 567: 568:
569: public static function dump($var, $return = FALSE)
570: {
571: if (!$return && self::$productionMode) {
572: return $var;
573: }
574:
575: $output = "<pre class=\"nette-dump\">" . DebugHelpers::htmlDump($var) . "</pre>\n";
576:
577: if (!$return && self::$showLocation) {
578: $trace = debug_backtrace();
579: $i = isset($trace[1]['class']) && $trace[1]['class'] === __CLASS__ ? 1 : 0;
580: if (isset($trace[$i]['file'], $trace[$i]['line'])) {
581: $output = substr_replace($output, ' <small>' . htmlspecialchars("in file {$trace[$i]['file']} on line {$trace[$i]['line']}", ENT_NOQUOTES) . '</small>', -8, 0);
582: }
583: }
584:
585: if (self::$consoleMode) {
586: $output = htmlspecialchars_decode(strip_tags($output), ENT_NOQUOTES);
587: }
588:
589: if ($return) {
590: return $output;
591:
592: } else {
593: echo $output;
594: return $var;
595: }
596: }
597:
598:
599:
600: 601: 602: 603: 604:
605: public static function timer($name = NULL)
606: {
607: static $time = array();
608: $now = microtime(TRUE);
609: $delta = isset($time[$name]) ? $now - $time[$name] : 0;
610: $time[$name] = $now;
611: return $delta;
612: }
613:
614:
615:
616:
617:
618:
619:
620: 621: 622: 623: 624:
625: public static function addPanel(IDebugPanel $panel)
626: {
627: self::$panels[] = $panel;
628: }
629:
630:
631:
632: 633: 634: 635: 636: 637:
638: public static function barDump($var, $title = NULL)
639: {
640: if (!self::$productionMode) {
641: $dump = array();
642: foreach ((is_array($var) ? $var : array('' => $var)) as $key => $val) {
643: $dump[$key] = DebugHelpers::clickableDump($val);
644: }
645: self::$panels[3]->data[] = array('title' => $title, 'dump' => $dump);
646: }
647: return $var;
648: }
649:
650:
651:
652:
653:
654:
655:
656: 657: 658: 659: 660: 661:
662: public static function fireLog($message)
663: {
664: if (self::$productionMode) {
665: return;
666:
667: } elseif (!self::$firebugDetected || headers_sent()) {
668: return FALSE;
669: }
670:
671: static $payload = array('logs' => array());
672:
673: $item = array(
674: 'name' => 'PHP',
675: 'level' => 'debug',
676: 'order' => count($payload['logs']),
677: 'time' => str_pad(number_format((microtime(TRUE) - self::$time) * 1000, 1, '.', ' '), 8, '0', STR_PAD_LEFT) . ' ms',
678: 'template' => '',
679: 'message' => '',
680: 'style' => 'background:#767ab6',
681: );
682:
683: $args = func_get_args();
684: if (isset($args[0]) && is_string($args[0])) {
685: $item['template'] = array_shift($args);
686: }
687:
688: if (isset($args[0]) && $args[0] instanceof \Exception) {
689: $e = array_shift($args);
690: $trace = $e->getTrace();
691: if (isset($trace[0]['class']) && $trace[0]['class'] === __CLASS__ && ($trace[0]['function'] === '_shutdownHandler' || $trace[0]['function'] === '_errorHandler')) {
692: unset($trace[0]);
693: }
694:
695: $item['exc_info'] = array(
696: $e->getMessage(),
697: $e->getFile(),
698: array(),
699: );
700: $item['exc_frames'] = array();
701:
702: foreach ($trace as $frame) {
703: $frame += array('file' => NULL, 'line' => NULL, 'class' => NULL, 'type' => NULL, 'function' => NULL, 'object' => NULL, 'args' => NULL);
704: $item['exc_info'][2][] = array($frame['file'], $frame['line'], "$frame[class]$frame[type]$frame[function]", $frame['object']);
705: $item['exc_frames'][] = $frame['args'];
706: }
707:
708: $file = str_replace(dirname(dirname(dirname($e->getFile()))), "\xE2\x80\xA6", $e->getFile());
709: $item['template'] = ($e instanceof \ErrorException ? '' : get_class($e) . ': ') . $e->getMessage() . ($e->getCode() ? ' #' . $e->getCode() : '') . ' in ' . $file . ':' . $e->getLine();
710: array_unshift($trace, array('file' => $e->getFile(), 'line' => $e->getLine()));
711:
712: } else {
713: $trace = debug_backtrace();
714: if (isset($trace[0]['class']) && $trace[0]['class'] === __CLASS__ && ($trace[0]['function'] === '_shutdownHandler' || $trace[0]['function'] === '_errorHandler')) {
715: unset($trace[0]);
716: }
717: }
718:
719: if (isset($args[0]) && in_array($args[0], array(self::DEBUG, self::INFO, self::WARNING, self::ERROR, self::CRITICAL), TRUE)) {
720: $item['level'] = array_shift($args);
721: }
722:
723: $item['args'] = $args;
724:
725: foreach ($trace as $frame) {
726: if (isset($frame['file']) && is_file($frame['file'])) {
727: $item['pathname'] = $frame['file'];
728: $item['lineno'] = $frame['line'];
729: break;
730: }
731: }
732:
733: $payload['logs'][] = DebugHelpers::jsonDump($item, -1);
734: foreach (str_split(base64_encode(@json_encode($payload)), 4990) as $k => $v) {
735: header("FireLogger-de11e-$k:$v");
736: }
737: return TRUE;
738: }
739:
740: }
741:
742:
743:
744: Debug::_init();
745: