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