1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Tracy;
9:
10:
11: 12: 13:
14: class Bar
15: {
16:
17: private $panels = [];
18:
19:
20: private $useSession;
21:
22:
23: 24: 25: 26: 27: 28:
29: public function addPanel(IBarPanel $panel, $id = NULL)
30: {
31: if ($id === NULL) {
32: $c = 0;
33: do {
34: $id = get_class($panel) . ($c++ ? "-$c" : '');
35: } while (isset($this->panels[$id]));
36: }
37: $this->panels[$id] = $panel;
38: return $this;
39: }
40:
41:
42: 43: 44: 45: 46:
47: public function getPanel($id)
48: {
49: return isset($this->panels[$id]) ? $this->panels[$id] : NULL;
50: }
51:
52:
53: 54: 55: 56:
57: public function render()
58: {
59: $useSession = $this->useSession && session_status() === PHP_SESSION_ACTIVE;
60: $redirectQueue = &$_SESSION['_tracy']['redirect'];
61:
62: foreach (['bar', 'redirect', 'bluescreen'] as $key) {
63: $queue = &$_SESSION['_tracy'][$key];
64: $queue = array_slice((array) $queue, -10, NULL, TRUE);
65: $queue = array_filter($queue, function ($item) {
66: return isset($item['time']) && $item['time'] > time() - 60;
67: });
68: }
69:
70: if (!Helpers::isHtmlMode() && !Helpers::isAjax()) {
71: return;
72:
73: } elseif (Helpers::isAjax()) {
74: $rows[] = (object) ['type' => 'ajax', 'panels' => $this->renderPanels('-ajax')];
75: $dumps = Dumper::fetchLiveData();
76: $contentId = $useSession ? $_SERVER['HTTP_X_TRACY_AJAX'] . '-ajax' : NULL;
77:
78: } elseif (preg_match('#^Location:#im', implode("\n", headers_list()))) {
79: Dumper::fetchLiveData();
80: Dumper::$livePrefix = count($redirectQueue) . 'p';
81: $redirectQueue[] = [
82: 'panels' => $this->renderPanels('-r' . count($redirectQueue)),
83: 'dumps' => Dumper::fetchLiveData(),
84: 'time' => time(),
85: ];
86: return;
87:
88: } else {
89: $rows[] = (object) ['type' => 'main', 'panels' => $this->renderPanels()];
90: $dumps = Dumper::fetchLiveData();
91: foreach (array_reverse((array) $redirectQueue) as $info) {
92: $rows[] = (object) ['type' => 'redirect', 'panels' => $info['panels']];
93: $dumps += $info['dumps'];
94: }
95: $redirectQueue = NULL;
96: $contentId = $useSession ? substr(md5(uniqid('', TRUE)), 0, 10) : NULL;
97: }
98:
99: ob_start(function () {});
100: require __DIR__ . '/assets/Bar/panels.phtml';
101: require __DIR__ . '/assets/Bar/bar.phtml';
102: $content = Helpers::fixEncoding(ob_get_clean());
103:
104: if ($contentId) {
105: $_SESSION['_tracy']['bar'][$contentId] = ['content' => $content, 'dumps' => $dumps, 'time' => time()];
106: }
107:
108: if (Helpers::isHtmlMode()) {
109: $nonce = Helpers::getNonce();
110: require __DIR__ . '/assets/Bar/loader.phtml';
111: }
112: }
113:
114:
115: 116: 117:
118: private function renderPanels($suffix = NULL)
119: {
120: set_error_handler(function ($severity, $message, $file, $line) {
121: if (error_reporting() & $severity) {
122: throw new \ErrorException($message, 0, $severity, $file, $line);
123: }
124: });
125:
126: $obLevel = ob_get_level();
127: $panels = [];
128:
129: foreach ($this->panels as $id => $panel) {
130: $idHtml = preg_replace('#[^a-z0-9]+#i', '-', $id) . $suffix;
131: try {
132: $tab = (string) $panel->getTab();
133: $panelHtml = $tab ? (string) $panel->getPanel() : NULL;
134: if ($tab && $panel instanceof \Nette\Diagnostics\IBarPanel) {
135: $e = new \Exception('Support for Nette\Diagnostics\IBarPanel is deprecated');
136: }
137:
138: } catch (\Throwable $e) {
139: } catch (\Exception $e) {
140: }
141: if (isset($e)) {
142: while (ob_get_level() > $obLevel) {
143: ob_end_clean();
144: }
145: $idHtml = "error-$idHtml";
146: $tab = "Error in $id";
147: $panelHtml = "<h1>Error: $id</h1><div class='tracy-inner'>" . nl2br(Helpers::escapeHtml($e)) . '</div>';
148: unset($e);
149: }
150: $panels[] = (object) ['id' => $idHtml, 'tab' => $tab, 'panel' => $panelHtml];
151: }
152:
153: restore_error_handler();
154: return $panels;
155: }
156:
157:
158: 159: 160: 161:
162: public function dispatchAssets()
163: {
164: $asset = isset($_GET['_tracy_bar']) ? $_GET['_tracy_bar'] : NULL;
165: if ($asset === 'js') {
166: header('Content-Type: text/javascript');
167: header('Cache-Control: max-age=864000');
168: header_remove('Pragma');
169: header_remove('Set-Cookie');
170: $this->renderAssets();
171: return TRUE;
172: }
173:
174: $this->useSession = session_status() === PHP_SESSION_ACTIVE;
175:
176: if ($this->useSession && Helpers::isAjax()) {
177: header('X-Tracy-Ajax: 1');
178: }
179:
180: if ($this->useSession && $asset && preg_match('#^content(-ajax)?.(\w+)$#', $asset, $m)) {
181: $session = &$_SESSION['_tracy']['bar'][$m[2] . $m[1]];
182: header('Content-Type: text/javascript');
183: header('Cache-Control: max-age=60');
184: header_remove('Set-Cookie');
185: if (!$m[1]) {
186: $this->renderAssets();
187: }
188: if ($session) {
189: $method = $m[1] ? 'loadAjax' : 'init';
190: echo "Tracy.Debug.$method(", json_encode($session['content']), ', ', json_encode($session['dumps']), ');';
191: $session = NULL;
192: }
193: $session = &$_SESSION['_tracy']['bluescreen'][$m[2]];
194: if ($session) {
195: echo "Tracy.BlueScreen.loadAjax(", json_encode($session['content']), ', ', json_encode($session['dumps']), ');';
196: $session = NULL;
197: }
198: return TRUE;
199: }
200: }
201:
202:
203: private function renderAssets()
204: {
205: $css = array_map('file_get_contents', [
206: __DIR__ . '/assets/Bar/bar.css',
207: __DIR__ . '/assets/Toggle/toggle.css',
208: __DIR__ . '/assets/Dumper/dumper.css',
209: __DIR__ . '/assets/BlueScreen/bluescreen.css',
210: ]);
211: $css = json_encode(preg_replace('#\s+#u', ' ', implode($css)));
212: echo "(function(){var el = document.createElement('style'); el.className='tracy-debug'; el.textContent=$css; document.head.appendChild(el);})();\n";
213:
214: array_map('readfile', [
215: __DIR__ . '/assets/Bar/bar.js',
216: __DIR__ . '/assets/Toggle/toggle.js',
217: __DIR__ . '/assets/Dumper/dumper.js',
218: __DIR__ . '/assets/BlueScreen/bluescreen.js',
219: ]);
220: }
221:
222: }
223: