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