PageRenderTime 68ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 1ms

/libs/Nette/Debug/Debug.php

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