1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Tracy;
9:
10:
11: 12: 13:
14: class BlueScreen
15: {
16:
17: public $info = [];
18:
19:
20: private $panels = [];
21:
22:
23: public $collapsePaths = [];
24:
25:
26: public $maxDepth = 3;
27:
28:
29: public $maxLength = 150;
30:
31:
32: public function __construct()
33: {
34: $this->collapsePaths[] = preg_match('#(.+/vendor)/tracy/tracy/src/Tracy$#', strtr(__DIR__, '\\', '/'), $m)
35: ? $m[1]
36: : __DIR__;
37: }
38:
39:
40: 41: 42: 43: 44:
45: public function addPanel($panel)
46: {
47: if (!in_array($panel, $this->panels, TRUE)) {
48: $this->panels[] = $panel;
49: }
50: return $this;
51: }
52:
53:
54: 55: 56: 57: 58:
59: public function render($exception)
60: {
61: if (Helpers::isAjax() && session_status() === PHP_SESSION_ACTIVE) {
62: ob_start(function () {});
63: $this->renderTemplate($exception, __DIR__ . '/assets/BlueScreen/content.phtml');
64: $contentId = $_SERVER['HTTP_X_TRACY_AJAX'];
65: $queue = & $_SESSION['_tracy']['bluescreen'];
66: $queue = array_slice(array_filter((array) $queue), -5, NULL, TRUE);
67: $queue[$contentId] = ['content' => ob_get_clean(), 'dumps' => Dumper::fetchLiveData()];
68:
69: } else {
70: $this->renderTemplate($exception, __DIR__ . '/assets/BlueScreen/page.phtml');
71: }
72: }
73:
74:
75: 76: 77: 78: 79: 80:
81: public function renderToFile($exception, $file)
82: {
83: if ($handle = @fopen($file, 'x')) {
84: ob_start();
85: ob_start(function ($buffer) use ($handle) { fwrite($handle, $buffer); }, 4096);
86: $this->renderTemplate($exception, __DIR__ . '/assets/BlueScreen/page.phtml');
87: ob_end_flush();
88: ob_end_clean();
89: fclose($handle);
90: }
91: }
92:
93:
94: private function renderTemplate($exception, $template)
95: {
96: $info = array_filter($this->info);
97: $source = Helpers::getSource();
98: $sourceIsUrl = preg_match('#^https?://#', $source);
99: $title = $exception instanceof \ErrorException
100: ? Helpers::errorTypeToString($exception->getSeverity())
101: : Helpers::getClass($exception);
102: $skipError = $sourceIsUrl && $exception instanceof \ErrorException && !empty($exception->skippable)
103: ? $source . (strpos($source, '?') ? '&' : '?') . '_tracy_skip_error'
104: : NULL;
105: $lastError = $exception instanceof \ErrorException || $exception instanceof \Error ? NULL : error_get_last();
106: $dump = function($v) {
107: return Dumper::toHtml($v, [
108: Dumper::DEPTH => $this->maxDepth,
109: Dumper::TRUNCATE => $this->maxLength,
110: Dumper::LIVE => TRUE,
111: Dumper::LOCATION => Dumper::LOCATION_CLASS,
112: ]);
113: };
114:
115: require $template;
116: }
117:
118:
119: 120: 121:
122: private function renderPanels($ex)
123: {
124: $obLevel = ob_get_level();
125: $res = [];
126: foreach ($this->panels as $callback) {
127: try {
128: $panel = call_user_func($callback, $ex);
129: if (empty($panel['tab']) || empty($panel['panel'])) {
130: continue;
131: }
132: $res[] = (object) $panel;
133: continue;
134: } catch (\Throwable $e) {
135: } catch (\Exception $e) {
136: }
137: while (ob_get_level() > $obLevel) {
138: ob_end_clean();
139: }
140: is_callable($callback, TRUE, $name);
141: $res[] = (object) [
142: 'tab' => "Error in panel $name",
143: 'panel' => nl2br(Helpers::escapeHtml($e)),
144: ];
145: }
146: return $res;
147: }
148:
149:
150: 151: 152: 153: 154: 155: 156:
157: public static function highlightFile($file, $line, $lines = 15, array $vars = NULL)
158: {
159: $source = @file_get_contents($file);
160: if ($source) {
161: $source = static::highlightPhp($source, $line, $lines, $vars);
162: if ($editor = Helpers::editorUri($file, $line)) {
163: $source = substr_replace($source, ' data-tracy-href="' . Helpers::escapeHtml($editor) . '"', 4, 0);
164: }
165: return $source;
166: }
167: }
168:
169:
170: 171: 172: 173: 174: 175: 176:
177: public static function highlightPhp($source, $line, $lines = 15, array $vars = NULL)
178: {
179: if (function_exists('ini_set')) {
180: ini_set('highlight.comment', '#998; font-style: italic');
181: ini_set('highlight.default', '#000');
182: ini_set('highlight.html', '#06B');
183: ini_set('highlight.keyword', '#D24; font-weight: bold');
184: ini_set('highlight.string', '#080');
185: }
186:
187: $source = str_replace(["\r\n", "\r"], "\n", $source);
188: $source = explode("\n", highlight_string($source, TRUE));
189: $out = $source[0];
190: $source = str_replace('<br />', "\n", $source[1]);
191: $out .= static::highlightLine($source, $line, $lines);
192:
193: if ($vars) {
194: $out = preg_replace_callback('#">\$(\w+)( )?</span>#', function ($m) use ($vars) {
195: return array_key_exists($m[1], $vars)
196: ? '" title="'
197: . str_replace('"', '"', trim(strip_tags(Dumper::toHtml($vars[$m[1]], [Dumper::DEPTH => 1]))))
198: . $m[0]
199: : $m[0];
200: }, $out);
201: }
202:
203: $out = str_replace(' ', ' ', $out);
204: return "<pre class='code'><div>$out</div></pre>";
205: }
206:
207:
208:
209: 210: 211: 212:
213: public static function highlightLine($html, $line, $lines = 15)
214: {
215: $source = explode("\n", "\n" . str_replace("\r\n", "\n", $html));
216: $out = '';
217: $spans = 1;
218: $start = $i = max(1, min($line, count($source) - 1) - floor($lines * 2 / 3));
219: while (--$i >= 1) {
220: if (preg_match('#.*(</?span[^>]*>)#', $source[$i], $m)) {
221: if ($m[1] !== '</span>') {
222: $spans++;
223: $out .= $m[1];
224: }
225: break;
226: }
227: }
228:
229: $source = array_slice($source, $start, $lines, TRUE);
230: end($source);
231: $numWidth = strlen((string) key($source));
232:
233: foreach ($source as $n => $s) {
234: $spans += substr_count($s, '<span') - substr_count($s, '</span');
235: $s = str_replace(["\r", "\n"], ['', ''], $s);
236: preg_match_all('#<[^>]+>#', $s, $tags);
237: if ($n == $line) {
238: $out .= sprintf(
239: "<span class='highlight'>%{$numWidth}s: %s\n</span>%s",
240: $n,
241: strip_tags($s),
242: implode('', $tags[0])
243: );
244: } else {
245: $out .= sprintf("<span class='line'>%{$numWidth}s:</span> %s\n", $n, $s);
246: }
247: }
248: $out .= str_repeat('</span>', $spans) . '</code>';
249: return $out;
250: }
251:
252:
253: 254: 255: 256: 257:
258: public function isCollapsed($file)
259: {
260: $file = strtr($file, '\\', '/') . '/';
261: foreach ($this->collapsePaths as $path) {
262: $path = strtr($path, '\\', '/') . '/';
263: if (strncmp($file, $path, strlen($path)) === 0) {
264: return TRUE;
265: }
266: }
267: return FALSE;
268: }
269:
270: }
271: