PageRenderTime 58ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/libs/Nette/Debug/Debug.php

https://code.google.com/
PHP | 963 lines | 604 code | 179 blank | 180 comment | 98 complexity | ab992b2281092fd6ea13a9ffb55de867 MD5 | raw file
Possible License(s): Apache-2.0, GPL-2.0
  1. <?php
  2. /**
  3. * This file is part of the Nette Framework.
  4. *
  5. * Copyright (c) 2004, 2010 David Grudl (http://davidgrudl.com)
  6. *
  7. * This source file is subject to the "Nette license", and/or
  8. * GPL license. For more information please see http://nette.org
  9. */
  10. namespace Nette;
  11. use Nette,
  12. Nette\Environment;
  13. /**
  14. * Debug static class.
  15. *
  16. * @author David Grudl
  17. */
  18. final class Debug
  19. {
  20. /** @var bool determines whether a server is running in production mode */
  21. public static $productionMode;
  22. /** @var bool determines whether a server is running in console mode */
  23. public static $consoleMode;
  24. /** @var int */
  25. public static $time;
  26. /** @var bool is Firebug & FirePHP detected? */
  27. private static $firebugDetected;
  28. /** @var bool is AJAX request detected? */
  29. private static $ajaxDetected;
  30. /** @var array payload filled by {@link Debug::consoleDump()} */
  31. private static $consoleData;
  32. /********************* Debug::dump() ****************d*g**/
  33. /** @var int how many nested levels of array/object properties display {@link Debug::dump()} */
  34. public static $maxDepth = 3;
  35. /** @var int how long strings display {@link Debug::dump()} */
  36. public static $maxLen = 150;
  37. /** @var int display location? {@link Debug::dump()} */
  38. public static $showLocation = FALSE;
  39. /********************* errors and exceptions reporing ****************d*g**/
  40. /**#@+ server modes {@link Debug::enable()} */
  41. const DEVELOPMENT = FALSE;
  42. const PRODUCTION = TRUE;
  43. const DETECT = NULL;
  44. /**#@-*/
  45. /** @var bool determines whether to consider all errors as fatal */
  46. public static $strictMode = FALSE;
  47. /** @var array of callbacks specifies the functions that are automatically called after fatal error */
  48. public static $onFatalError = array();
  49. /** @var callback */
  50. public static $mailer = array(__CLASS__, 'defaultMailer');
  51. /** @var int interval for sending email is 2 days */
  52. public static $emailSnooze = 172800;
  53. /** @var bool {@link Debug::enable()} */
  54. private static $enabled = FALSE;
  55. /** @var string name of the file where script errors should be logged */
  56. private static $logFile;
  57. /** @var resource */
  58. private static $logHandle;
  59. /** @var bool send e-mail notifications of errors? */
  60. private static $sendEmails;
  61. /** @var string e-mail headers & body */
  62. private static $emailHeaders = array(
  63. 'To' => '',
  64. 'From' => 'noreply@%host%',
  65. 'X-Mailer' => 'Nette Framework',
  66. 'Subject' => 'PHP: An error occurred on the server %host%',
  67. 'Body' => '[%date%] %message%',
  68. );
  69. /** @var array */
  70. private static $colophons = array(array(__CLASS__, 'getDefaultColophons'));
  71. /********************* profiler ****************d*g**/
  72. /** @var bool {@link Debug::enableProfiler()} */
  73. private static $enabledProfiler = FALSE;
  74. /** @var array free counters for your usage */
  75. public static $counters = array();
  76. /********************* Firebug extension ****************d*g**/
  77. /**#@+ FirePHP log priority */
  78. const LOG = 'LOG';
  79. const INFO = 'INFO';
  80. const WARN = 'WARN';
  81. const ERROR = 'ERROR';
  82. const TRACE = 'TRACE';
  83. const EXCEPTION = 'EXCEPTION';
  84. const GROUP_START = 'GROUP_START';
  85. const GROUP_END = 'GROUP_END';
  86. /**#@-*/
  87. /**
  88. * Static class - cannot be instantiated.
  89. */
  90. final public function __construct()
  91. {
  92. throw new \LogicException("Cannot instantiate static class " . get_class($this));
  93. }
  94. /**
  95. * Static class constructor.
  96. * @internal
  97. */
  98. public static function _init()
  99. {
  100. self::$time = microtime(TRUE);
  101. self::$consoleMode = PHP_SAPI === 'cli';
  102. self::$productionMode = self::DETECT;
  103. self::$firebugDetected = isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'FirePHP/');
  104. self::$ajaxDetected = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest';
  105. register_shutdown_function(array(__CLASS__, '_shutdownHandler'));
  106. }
  107. /**
  108. * Shutdown handler to execute of the planned activities.
  109. * @return void
  110. * @internal
  111. */
  112. public static function _shutdownHandler()
  113. {
  114. // 1) fatal error handler
  115. static $types = array(
  116. E_ERROR => 1,
  117. E_CORE_ERROR => 1,
  118. E_COMPILE_ERROR => 1,
  119. E_PARSE => 1,
  120. );
  121. $error = error_get_last();
  122. if (self::$enabled && isset($types[$error['type']])) {
  123. if (!headers_sent()) { // for PHP < 5.2.4
  124. header('HTTP/1.1 500 Internal Server Error');
  125. }
  126. if (ini_get('html_errors')) {
  127. $error['message'] = html_entity_decode(strip_tags($error['message']), ENT_QUOTES, 'UTF-8');
  128. }
  129. self::processException(new \FatalErrorException($error['message'], 0, $error['type'], $error['file'], $error['line'], NULL), TRUE);
  130. }
  131. // other activities require HTML & development mode
  132. if (self::$productionMode) {
  133. return;
  134. }
  135. foreach (headers_list() as $header) {
  136. if (strncasecmp($header, 'Content-Type:', 13) === 0) {
  137. if (substr($header, 14, 9) === 'text/html') {
  138. break;
  139. }
  140. return;
  141. }
  142. }
  143. // 2) profiler
  144. if (self::$enabledProfiler) {
  145. if (self::$firebugDetected) {
  146. self::fireLog('Nette profiler', self::GROUP_START);
  147. foreach (self::$colophons as $callback) {
  148. foreach ((array) call_user_func($callback, 'profiler') as $line) self::fireLog(strip_tags($line));
  149. }
  150. self::fireLog(NULL, self::GROUP_END);
  151. }
  152. if (!self::$ajaxDetected) {
  153. $colophons = self::$colophons;
  154. require __DIR__ . '/templates/profiler.phtml';
  155. }
  156. }
  157. // 3) console
  158. if (self::$consoleData) {
  159. $payload = self::$consoleData;
  160. require __DIR__ . '/templates/console.phtml';
  161. }
  162. }
  163. /********************* useful tools ****************d*g**/
  164. /**
  165. * Dumps information about a variable in readable format.
  166. * @param mixed variable to dump
  167. * @param bool return output instead of printing it? (bypasses $productionMode)
  168. * @return mixed variable itself or dump
  169. */
  170. public static function dump($var, $return = FALSE)
  171. {
  172. if (!$return && self::$productionMode) {
  173. return $var;
  174. }
  175. $output = "<pre class=\"dump\">" . self::_dump($var, 0) . "</pre>\n";
  176. if (!$return && self::$showLocation) {
  177. $trace = debug_backtrace();
  178. $i = isset($trace[1]['class']) && $trace[1]['class'] === __CLASS__ ? 1 : 0;
  179. if (isset($trace[$i]['file'], $trace[$i]['line'])) {
  180. $output = substr_replace($output, ' <small>' . htmlspecialchars("in file {$trace[$i]['file']} on line {$trace[$i]['line']}", ENT_NOQUOTES) . '</small>', -8, 0);
  181. }
  182. }
  183. if (self::$consoleMode) {
  184. $output = htmlspecialchars_decode(strip_tags($output), ENT_NOQUOTES);
  185. }
  186. if ($return) {
  187. return $output;
  188. } else {
  189. echo $output;
  190. return $var;
  191. }
  192. }
  193. /**
  194. * Dumps information about a variable in Nette Debug Console.
  195. * @param mixed variable to dump
  196. * @param string optional title
  197. * @return mixed variable itself
  198. */
  199. public static function consoleDump($var, $title = NULL)
  200. {
  201. if (!self::$productionMode) {
  202. $dump = array();
  203. foreach ((is_array($var) ? $var : array('' => $var)) as $key => $val) {
  204. $dump[$key] = self::dump($val, TRUE);
  205. }
  206. self::$consoleData[] = array('title' => $title, 'dump' => $dump);
  207. }
  208. return $var;
  209. }
  210. /**
  211. * Internal dump() implementation.
  212. * @param mixed variable to dump
  213. * @param int current recursion level
  214. * @return string
  215. */
  216. private static function _dump(&$var, $level)
  217. {
  218. static $tableUtf, $tableBin, $reBinary = '#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}]#u';
  219. if ($tableUtf === NULL) {
  220. foreach (range("\x00", "\xFF") as $ch) {
  221. if (ord($ch) < 32 && strpos("\r\n\t", $ch) === FALSE) $tableUtf[$ch] = $tableBin[$ch] = '\\x' . str_pad(dechex(ord($ch)), 2, '0', STR_PAD_LEFT);
  222. elseif (ord($ch) < 127) $tableUtf[$ch] = $tableBin[$ch] = $ch;
  223. else { $tableUtf[$ch] = $ch; $tableBin[$ch] = '\\x' . dechex(ord($ch)); }
  224. }
  225. $tableBin["\\"] = '\\\\';
  226. $tableBin["\r"] = '\\r';
  227. $tableBin["\n"] = '\\n';
  228. $tableBin["\t"] = '\\t';
  229. $tableUtf['\\x'] = $tableBin['\\x'] = '\\\\x';
  230. }
  231. if (is_bool($var)) {
  232. return ($var ? 'TRUE' : 'FALSE') . "\n";
  233. } elseif ($var === NULL) {
  234. return "NULL\n";
  235. } elseif (is_int($var)) {
  236. return "$var\n";
  237. } elseif (is_float($var)) {
  238. $var = (string) $var;
  239. if (strpos($var, '.') === FALSE) $var .= '.0';
  240. return "$var\n";
  241. } elseif (is_string($var)) {
  242. if (self::$maxLen && strlen($var) > self::$maxLen) {
  243. $s = htmlSpecialChars(substr($var, 0, self::$maxLen), ENT_NOQUOTES) . ' ... ';
  244. } else {
  245. $s = htmlSpecialChars($var, ENT_NOQUOTES);
  246. }
  247. $s = strtr($s, preg_match($reBinary, $s) || preg_last_error() ? $tableBin : $tableUtf);
  248. $len = strlen($var);
  249. return "\"$s\"" . ($len > 1 ? " ($len)" : "") . "\n";
  250. } elseif (is_array($var)) {
  251. $s = "<span>array</span>(" . count($var) . ") ";
  252. $space = str_repeat($space1 = ' ', $level);
  253. $brackets = range(0, count($var) - 1) === array_keys($var) ? "[]" : "{}";
  254. static $marker;
  255. if ($marker === NULL) $marker = uniqid("\x00", TRUE);
  256. if (empty($var)) {
  257. } elseif (isset($var[$marker])) {
  258. $brackets = $var[$marker];
  259. $s .= "$brackets[0] *RECURSION* $brackets[1]";
  260. } elseif ($level < self::$maxDepth || !self::$maxDepth) {
  261. $s .= "<code>$brackets[0]\n";
  262. $var[$marker] = $brackets;
  263. foreach ($var as $k => &$v) {
  264. if ($k === $marker) continue;
  265. $k = is_int($k) ? $k : '"' . strtr($k, preg_match($reBinary, $k) || preg_last_error() ? $tableBin : $tableUtf) . '"';
  266. $s .= "$space$space1$k => " . self::_dump($v, $level + 1);
  267. }
  268. unset($var[$marker]);
  269. $s .= "$space$brackets[1]</code>";
  270. } else {
  271. $s .= "$brackets[0] ... $brackets[1]";
  272. }
  273. return $s . "\n";
  274. } elseif (is_object($var)) {
  275. $arr = (array) $var;
  276. $s = "<span>" . get_class($var) . "</span>(" . count($arr) . ") ";
  277. $space = str_repeat($space1 = ' ', $level);
  278. static $list = array();
  279. if (empty($arr)) {
  280. } elseif (in_array($var, $list, TRUE)) {
  281. $s .= "{ *RECURSION* }";
  282. } elseif ($level < self::$maxDepth || !self::$maxDepth) {
  283. $s .= "<code>{\n";
  284. $list[] = $var;
  285. foreach ($arr as $k => &$v) {
  286. $m = '';
  287. if ($k[0] === "\x00") {
  288. $m = $k[1] === '*' ? ' <span>protected</span>' : ' <span>private</span>';
  289. $k = substr($k, strrpos($k, "\x00") + 1);
  290. }
  291. $k = strtr($k, preg_match($reBinary, $k) || preg_last_error() ? $tableBin : $tableUtf);
  292. $s .= "$space$space1\"$k\"$m => " . self::_dump($v, $level + 1);
  293. }
  294. array_pop($list);
  295. $s .= "$space}</code>";
  296. } else {
  297. $s .= "{ ... }";
  298. }
  299. return $s . "\n";
  300. } elseif (is_resource($var)) {
  301. return "<span>" . get_resource_type($var) . " resource</span>\n";
  302. } else {
  303. return "<span>unknown type</span>\n";
  304. }
  305. }
  306. /**
  307. * Starts/stops stopwatch.
  308. * @param string name
  309. * @return elapsed seconds
  310. */
  311. public static function timer($name = NULL)
  312. {
  313. static $time = array();
  314. $now = microtime(TRUE);
  315. $delta = isset($time[$name]) ? $now - $time[$name] : 0;
  316. $time[$name] = $now;
  317. return $delta;
  318. }
  319. /********************* errors and exceptions reporing ****************d*g**/
  320. /**
  321. * Enables displaying or logging errors and exceptions.
  322. * @param mixed production, development mode, autodetection or IP address(es).
  323. * @param string error log file (FALSE disables logging in production mode)
  324. * @param array|string administrator email or email headers; enables email sending in production mode
  325. * @return void
  326. */
  327. public static function enable($mode = NULL, $logFile = NULL, $email = NULL)
  328. {
  329. error_reporting(E_ALL | E_STRICT);
  330. // production/development mode detection
  331. if (is_bool($mode)) {
  332. self::$productionMode = $mode;
  333. } elseif (is_string($mode)) { // IP adresses
  334. $mode = preg_split('#[,\s]+#', $mode);
  335. }
  336. if (is_array($mode)) { // IP adresses
  337. self::$productionMode = !isset($_SERVER['REMOTE_ADDR']) || !in_array($_SERVER['REMOTE_ADDR'], $mode, TRUE);
  338. }
  339. if (self::$productionMode === self::DETECT) {
  340. if (class_exists('Nette\Environment')) {
  341. self::$productionMode = Environment::isProduction();
  342. } elseif (isset($_SERVER['SERVER_ADDR']) || isset($_SERVER['LOCAL_ADDR'])) { // IP address based detection
  343. $addr = isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : $_SERVER['LOCAL_ADDR'];
  344. $oct = explode('.', $addr);
  345. self::$productionMode = $addr !== '::1' && (count($oct) !== 4 || ($oct[0] !== '10' && $oct[0] !== '127' && ($oct[0] !== '172' || $oct[1] < 16 || $oct[1] > 31)
  346. && ($oct[0] !== '169' || $oct[1] !== '254') && ($oct[0] !== '192' || $oct[1] !== '168')));
  347. } else {
  348. self::$productionMode = !self::$consoleMode;
  349. }
  350. }
  351. // logging configuration
  352. if (self::$productionMode && $logFile !== FALSE) {
  353. self::$logFile = 'log/php_error.log';
  354. if (class_exists('Nette\Environment')) {
  355. if (is_string($logFile)) {
  356. self::$logFile = Environment::expand($logFile);
  357. } else try {
  358. self::$logFile = Environment::expand('%logDir%/php_error.log');
  359. } catch (\InvalidStateException $e) {
  360. }
  361. } elseif (is_string($logFile)) {
  362. self::$logFile = $logFile;
  363. }
  364. ini_set('error_log', self::$logFile);
  365. }
  366. // php configuration
  367. if (function_exists('ini_set')) {
  368. ini_set('display_errors', !self::$productionMode); // or 'stderr'
  369. ini_set('html_errors', !self::$logFile && !self::$consoleMode);
  370. ini_set('log_errors', (bool) self::$logFile);
  371. } elseif (ini_get('log_errors') != (bool) self::$logFile || // intentionally ==
  372. (ini_get('display_errors') != !self::$productionMode && ini_get('display_errors') !== (self::$productionMode ? 'stderr' : 'stdout'))) {
  373. throw new \LogicException('Function ini_set() must be enabled.');
  374. }
  375. self::$sendEmails = self::$logFile && $email;
  376. if (self::$sendEmails) {
  377. if (is_string($email)) {
  378. self::$emailHeaders['To'] = $email;
  379. } elseif (is_array($email)) {
  380. self::$emailHeaders = $email + self::$emailHeaders;
  381. }
  382. }
  383. if (!defined('E_DEPRECATED')) {
  384. define('E_DEPRECATED', 8192);
  385. }
  386. if (!defined('E_USER_DEPRECATED')) {
  387. define('E_USER_DEPRECATED', 16384);
  388. }
  389. set_exception_handler(array(__CLASS__, '_exceptionHandler'));
  390. set_error_handler(array(__CLASS__, '_errorHandler'));
  391. self::$enabled = TRUE;
  392. }
  393. /**
  394. * Is Debug enabled?
  395. * @return bool
  396. */
  397. public static function isEnabled()
  398. {
  399. return self::$enabled;
  400. }
  401. /**
  402. * Debug exception handler.
  403. * @param \Exception
  404. * @return void
  405. * @internal
  406. */
  407. public static function _exceptionHandler(\Exception $exception)
  408. {
  409. if (!headers_sent()) {
  410. header('HTTP/1.1 500 Internal Server Error');
  411. }
  412. self::processException($exception, TRUE);
  413. exit;
  414. }
  415. /**
  416. * Own error handler.
  417. * @param int level of the error raised
  418. * @param string error message
  419. * @param string file that the error was raised in
  420. * @param int line number the error was raised at
  421. * @param array an array of variables that existed in the scope the error was triggered in
  422. * @return bool FALSE to call normal error handler, NULL otherwise
  423. * @throws \FatalErrorException
  424. * @internal
  425. */
  426. public static function _errorHandler($severity, $message, $file, $line, $context)
  427. {
  428. if ($severity === E_RECOVERABLE_ERROR || $severity === E_USER_ERROR) {
  429. throw new \FatalErrorException($message, 0, $severity, $file, $line, $context);
  430. } elseif (($severity & error_reporting()) !== $severity) {
  431. return NULL; // nothing to do
  432. } elseif (self::$strictMode) {
  433. self::_exceptionHandler(new \FatalErrorException($message, 0, $severity, $file, $line, $context), TRUE);
  434. }
  435. static $types = array(
  436. E_WARNING => 'Warning',
  437. E_USER_WARNING => 'Warning',
  438. E_NOTICE => 'Notice',
  439. E_USER_NOTICE => 'Notice',
  440. E_STRICT => 'Strict standards',
  441. E_DEPRECATED => 'Deprecated',
  442. E_USER_DEPRECATED => 'Deprecated',
  443. );
  444. $type = isset($types[$severity]) ? $types[$severity] : 'Unknown error';
  445. if (self::$logFile) {
  446. if (self::$sendEmails) {
  447. self::sendEmail("$type: $message in $file on line $line");
  448. }
  449. return FALSE; // call normal error handler
  450. } elseif (!self::$productionMode && self::$firebugDetected && !headers_sent()) {
  451. $message = strip_tags($message);
  452. self::fireLog("$type: $message in $file on line $line", self::ERROR);
  453. return NULL;
  454. }
  455. return FALSE; // call normal error handler
  456. }
  457. /**
  458. * Logs or displays exception.
  459. * @param \Exception
  460. * @param bool is writing to standard output buffer allowed?
  461. * @return void
  462. */
  463. public static function processException(\Exception $exception, $outputAllowed = FALSE)
  464. {
  465. if (!self::$enabled) {
  466. return;
  467. } elseif (self::$logFile) {
  468. try {
  469. $hash = md5($exception );
  470. error_log("PHP Fatal error: Uncaught $exception");
  471. foreach (new \DirectoryIterator(dirname(self::$logFile)) as $entry) {
  472. if (strpos($entry, $hash)) {
  473. $skip = TRUE;
  474. break;
  475. }
  476. }
  477. $file = dirname(self::$logFile) . "/exception " . @date('Y-m-d H-i-s') . " $hash.html";
  478. if (empty($skip) && self::$logHandle = @fopen($file, 'x')) {
  479. ob_start(); // double buffer prevents sending HTTP headers in some PHP
  480. ob_start(array(__CLASS__, '_writeFile'), 1);
  481. self::_paintBlueScreen($exception);
  482. ob_end_flush();
  483. ob_end_clean();
  484. fclose(self::$logHandle);
  485. }
  486. if (self::$sendEmails) {
  487. self::sendEmail((string) $exception);
  488. }
  489. } catch (\Exception $e) {
  490. if (!headers_sent()) {
  491. header('HTTP/1.1 500 Internal Server Error');
  492. }
  493. echo 'Nette\Debug fatal error: ', get_class($e), ': ', ($e->getCode() ? '#' . $e->getCode() . ' ' : '') . $e->getMessage(), "\n";
  494. exit;
  495. }
  496. } elseif (self::$productionMode) {
  497. // be quiet
  498. } elseif (self::$consoleMode) { // dump to console
  499. if ($outputAllowed) {
  500. echo "$exception\n";
  501. foreach (self::$colophons as $callback) {
  502. foreach ((array) call_user_func($callback, 'bluescreen') as $line) echo strip_tags($line) . "\n";
  503. }
  504. }
  505. } elseif (self::$firebugDetected && self::$ajaxDetected && !headers_sent()) { // AJAX mode
  506. self::fireLog($exception, self::EXCEPTION);
  507. } elseif ($outputAllowed) { // dump to browser
  508. self::_paintBlueScreen($exception);
  509. } elseif (self::$firebugDetected && !headers_sent()) {
  510. self::fireLog($exception, self::EXCEPTION);
  511. }
  512. foreach (self::$onFatalError as $handler) {
  513. call_user_func($handler, $exception);
  514. }
  515. }
  516. /**
  517. * Handles exception throwed in __toString().
  518. * @param \Exception
  519. * @return void
  520. */
  521. public static function toStringException(\Exception $exception)
  522. {
  523. if (self::$enabled) {
  524. self::_exceptionHandler($exception);
  525. } else {
  526. trigger_error($exception->getMessage(), E_USER_ERROR);
  527. }
  528. }
  529. /**
  530. * Paint blue screen.
  531. * @param \Exception
  532. * @return void
  533. * @internal
  534. */
  535. public static function _paintBlueScreen(\Exception $exception)
  536. {
  537. if (class_exists('Nette\Environment', FALSE)) {
  538. $application = Environment::getServiceLocator()->hasService('Nette\\Application\\Application', TRUE) ? Environment::getServiceLocator()->getService('Nette\\Application\\Application') : NULL;
  539. }
  540. $colophons = self::$colophons;
  541. require __DIR__ . '/templates/bluescreen.phtml';
  542. }
  543. /**
  544. * Redirects output to file.
  545. * @param string
  546. * @return string
  547. * @internal
  548. */
  549. public static function _writeFile($buffer)
  550. {
  551. fwrite(self::$logHandle, $buffer);
  552. }
  553. /**
  554. * Sends e-mail notification.
  555. * @param string
  556. * @return void
  557. */
  558. private static function sendEmail($message)
  559. {
  560. $monitorFile = self::$logFile . '.monitor';
  561. if (@filemtime($monitorFile) + self::$emailSnooze < time() // @ - file may not exist
  562. && @file_put_contents($monitorFile, 'sent')) { // @ - file may not be writable
  563. call_user_func(self::$mailer, $message);
  564. }
  565. }
  566. /**
  567. * Default mailer.
  568. * @param string
  569. * @return void
  570. */
  571. private static function defaultMailer($message)
  572. {
  573. $host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] :
  574. (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '');
  575. $headers = str_replace(
  576. array('%host%', '%date%', '%message%'),
  577. array($host, @date('Y-m-d H:i:s', self::$time), $message), // @ - timezone may not be set
  578. self::$emailHeaders
  579. );
  580. $subject = $headers['Subject'];
  581. $to = $headers['To'];
  582. $body = str_replace("\r\n", PHP_EOL, $headers['Body']);
  583. unset($headers['Subject'], $headers['To'], $headers['Body']);
  584. $header = '';
  585. foreach ($headers as $key => $value) {
  586. $header .= "$key: $value" . PHP_EOL;
  587. }
  588. mail($to, $subject, $body, $header);
  589. }
  590. /********************* profiler ****************d*g**/
  591. /**
  592. * Enables profiler.
  593. * @return void
  594. */
  595. public static function enableProfiler()
  596. {
  597. self::$enabledProfiler = TRUE;
  598. }
  599. /**
  600. * Disables profiler.
  601. * @return void
  602. */
  603. public static function disableProfiler()
  604. {
  605. self::$enabledProfiler = FALSE;
  606. }
  607. /********************* colophons ****************d*g**/
  608. /**
  609. * Add custom descriptions.
  610. * @param callback
  611. * @return void
  612. */
  613. public static function addColophon($callback)
  614. {
  615. if (!is_callable($callback)) {
  616. $able = is_callable($callback, TRUE, $textual);
  617. throw new \InvalidArgumentException("Colophon handler '$textual' is not " . ($able ? 'callable.' : 'valid PHP callback.'));
  618. }
  619. if (!in_array($callback, self::$colophons, TRUE)) {
  620. self::$colophons[] = $callback;
  621. }
  622. }
  623. /**
  624. * Returns default colophons.
  625. * @param string profiler | bluescreen
  626. * @return array
  627. */
  628. private static function getDefaultColophons($sender)
  629. {
  630. if ($sender === 'profiler') {
  631. $arr[] = 'Elapsed time: <b>' . number_format((microtime(TRUE) - self::$time) * 1000, 1, '.', ' ') . '</b> ms | Allocated memory: <b>' . number_format(memory_get_peak_usage() / 1000, 1, '.', ' ') . '</b> kB';
  632. foreach ((array) self::$counters as $name => $value) {
  633. if (is_array($value)) $value = implode(', ', $value);
  634. $arr[] = htmlSpecialChars($name) . ' = <strong>' . htmlSpecialChars($value) . '</strong>';
  635. }
  636. $autoloaded = class_exists('Nette\Loaders\AutoLoader', FALSE) ? Nette\Loaders\AutoLoader::$count : 0;
  637. $s = '<span>' . count(get_included_files()) . '/' . $autoloaded . ' files</span>, ';
  638. $exclude = array('stdClass', 'Exception', 'ErrorException', 'Traversable', 'IteratorAggregate', 'Iterator', 'ArrayAccess', 'Serializable', 'Closure');
  639. foreach (get_loaded_extensions() as $ext) {
  640. $ref = new \ReflectionExtension($ext);
  641. $exclude = array_merge($exclude, $ref->getClassNames());
  642. }
  643. $classes = array_diff(get_declared_classes(), $exclude);
  644. $intf = array_diff(get_declared_interfaces(), $exclude);
  645. $func = get_defined_functions();
  646. $func = (array) @$func['user'];
  647. $consts = get_defined_constants(TRUE);
  648. $consts = array_keys((array) @$consts['user']);
  649. foreach (array('classes', 'intf', 'func', 'consts') as $item) {
  650. $s .= '<span ' . ($$item ? 'title="' . implode(", ", $$item) . '"' : '') . '>' . count($$item) . ' ' . $item . '</span>, ';
  651. }
  652. $arr[] = $s;
  653. }
  654. if ($sender === 'bluescreen') {
  655. $arr[] = 'Report generated at ' . @date('Y/m/d H:i:s', self::$time); // intentionally @
  656. if (isset($_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI'])) {
  657. $url = (isset($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'off') ? 'https://' : 'http://') . htmlSpecialChars($_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
  658. $arr[] = '<a href="' . $url . '">' . $url . '</a>';
  659. }
  660. $arr[] = 'PHP ' . htmlSpecialChars(PHP_VERSION);
  661. if (isset($_SERVER['SERVER_SOFTWARE'])) $arr[] = htmlSpecialChars($_SERVER['SERVER_SOFTWARE']);
  662. if (class_exists('Nette\Framework')) $arr[] = htmlSpecialChars('Nette Framework ' . Framework::VERSION) . ' <i>(revision ' . htmlSpecialChars(Framework::REVISION) . ')</i>';
  663. }
  664. return $arr;
  665. }
  666. /********************* Firebug extension ****************d*g**/
  667. /**
  668. * Sends variable dump to Firebug tab request/server.
  669. * @param mixed variable to dump
  670. * @param string unique key
  671. * @return mixed variable itself
  672. */
  673. public static function fireDump($var, $key)
  674. {
  675. self::fireSend('Dump/0.1', array((string) $key => $var));
  676. return $var;
  677. }
  678. /**
  679. * Sends message to Firebug console.
  680. * @param mixed message to log
  681. * @param string priority of message (LOG, INFO, WARN, ERROR, GROUP_START, GROUP_END)
  682. * @param string optional label
  683. * @return bool was successful?
  684. */
  685. public static function fireLog($message, $priority = self::LOG, $label = NULL)
  686. {
  687. if ($message instanceof \Exception) {
  688. if ($priority !== self::EXCEPTION && $priority !== self::TRACE) {
  689. $priority = self::TRACE;
  690. }
  691. $message = array(
  692. 'Class' => get_class($message),
  693. 'Message' => $message->getMessage(),
  694. 'File' => $message->getFile(),
  695. 'Line' => $message->getLine(),
  696. 'Trace' => $message->getTrace(),
  697. 'Type' => '',
  698. 'Function' => '',
  699. );
  700. foreach ($message['Trace'] as & $row) {
  701. if (empty($row['file'])) $row['file'] = '?';
  702. if (empty($row['line'])) $row['line'] = '?';
  703. }
  704. } elseif ($priority === self::GROUP_START) {
  705. $label = $message;
  706. $message = NULL;
  707. }
  708. return self::fireSend('FirebugConsole/0.1', self::replaceObjects(array(array('Type' => $priority, 'Label' => $label), $message)));
  709. }
  710. /**
  711. * Performs Firebug output.
  712. * @see http://www.firephp.org
  713. * @param string structure
  714. * @param array payload
  715. * @return bool was successful?
  716. */
  717. private static function fireSend($struct, $payload)
  718. {
  719. if (self::$productionMode) return NULL;
  720. if (headers_sent()) return FALSE; // or throw exception?
  721. header('X-Wf-Protocol-nette: http://meta.wildfirehq.org/Protocol/JsonStream/0.2');
  722. header('X-Wf-nette-Plugin-1: http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.2.0');
  723. static $structures;
  724. $index = isset($structures[$struct]) ? $structures[$struct] : ($structures[$struct] = count($structures) + 1);
  725. header("X-Wf-nette-Structure-$index: http://meta.firephp.org/Wildfire/Structure/FirePHP/$struct");
  726. $payload = json_encode($payload);
  727. static $counter;
  728. foreach (str_split($payload, 4990) as $s) {
  729. $num = ++$counter;
  730. header("X-Wf-nette-$index-1-n$num: |$s|\\");
  731. }
  732. header("X-Wf-nette-$index-1-n$num: |$s|");
  733. return TRUE;
  734. }
  735. /**
  736. * fireLog helper.
  737. * @param mixed
  738. * @return mixed
  739. */
  740. static private function replaceObjects($val)
  741. {
  742. if (is_object($val)) {
  743. return 'object ' . get_class($val) . '';
  744. } elseif (is_string($val)) {
  745. return @iconv('UTF-16', 'UTF-8//IGNORE', iconv('UTF-8', 'UTF-16//IGNORE', $val)); // intentionally @
  746. } elseif (is_array($val)) {
  747. foreach ($val as $k => $v) {
  748. unset($val[$k]);
  749. $k = @iconv('UTF-16', 'UTF-8//IGNORE', iconv('UTF-8', 'UTF-16//IGNORE', $k)); // intentionally @
  750. $val[$k] = self::replaceObjects($v);
  751. }
  752. }
  753. return $val;
  754. }
  755. }
  756. Debug::_init();