Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationDI
      • ApplicationLatte
      • ApplicationTracy
      • CacheDI
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsDI
      • FormsLatte
      • Framework
      • HttpDI
      • HttpTracy
      • MailDI
      • ReflectionDI
      • SecurityDI
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Conventions
      • Drivers
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
      • Traits
    • Reflection
    • Security
    • Tokenizer
    • Utils
  • Tracy
    • Bridges
      • Nette
  • none

Classes

  • Bar
  • BlueScreen
  • Debugger
  • DefaultBarPanel
  • Dumper
  • FireLogger
  • Helpers
  • Logger
  • OutputDebugger

Interfaces

  • IBarPanel
  • ILogger
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Tracy (https://tracy.nette.org)
  5:  * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
  6:  */
  7: 
  8: namespace Tracy;
  9: 
 10: 
 11: /**
 12:  * Red BlueScreen.
 13:  */
 14: class BlueScreen
 15: {
 16:     /** @var string[] */
 17:     public $info = [];
 18: 
 19:     /** @var string[] paths to be collapsed in stack trace (e.g. core libraries) */
 20:     public $collapsePaths = [];
 21: 
 22:     /** @var int  */
 23:     public $maxDepth = 3;
 24: 
 25:     /** @var int  */
 26:     public $maxLength = 150;
 27: 
 28:     /** @var string[] */
 29:     public $keysToHide = ['password', 'passwd', 'pass', 'pwd', 'creditcard', 'credit card', 'cc', 'pin'];
 30: 
 31:     /** @var callable[] */
 32:     private $panels = [];
 33: 
 34:     /** @var callable[] functions that returns action for exceptions */
 35:     private $actions = [];
 36: 
 37: 
 38:     public function __construct()
 39:     {
 40:         $this->collapsePaths[] = preg_match('#(.+/vendor)/tracy/tracy/src/Tracy$#', strtr(__DIR__, '\\', '/'), $m)
 41:             ? $m[1]
 42:             : __DIR__;
 43:     }
 44: 
 45: 
 46:     /**
 47:      * Add custom panel.
 48:      * @param  callable  $panel
 49:      * @return static
 50:      */
 51:     public function addPanel($panel)
 52:     {
 53:         if (!in_array($panel, $this->panels, true)) {
 54:             $this->panels[] = $panel;
 55:         }
 56:         return $this;
 57:     }
 58: 
 59: 
 60:     /**
 61:      * Add action.
 62:      * @param  callable  $action
 63:      * @return static
 64:      */
 65:     public function addAction($action)
 66:     {
 67:         $this->actions[] = $action;
 68:         return $this;
 69:     }
 70: 
 71: 
 72:     /**
 73:      * Renders blue screen.
 74:      * @param  \Exception|\Throwable  $exception
 75:      * @return void
 76:      */
 77:     public function render($exception)
 78:     {
 79:         if (Helpers::isAjax() && session_status() === PHP_SESSION_ACTIVE) {
 80:             ob_start(function () {});
 81:             $this->renderTemplate($exception, __DIR__ . '/assets/BlueScreen/content.phtml');
 82:             $contentId = $_SERVER['HTTP_X_TRACY_AJAX'];
 83:             $_SESSION['_tracy']['bluescreen'][$contentId] = ['content' => ob_get_clean(), 'dumps' => Dumper::fetchLiveData(), 'time' => time()];
 84: 
 85:         } else {
 86:             $this->renderTemplate($exception, __DIR__ . '/assets/BlueScreen/page.phtml');
 87:         }
 88:     }
 89: 
 90: 
 91:     /**
 92:      * Renders blue screen to file (if file exists, it will not be overwritten).
 93:      * @param  \Exception|\Throwable  $exception
 94:      * @param  string  $file file path
 95:      * @return void
 96:      */
 97:     public function renderToFile($exception, $file)
 98:     {
 99:         if ($handle = @fopen($file, 'x')) {
100:             ob_start(); // double buffer prevents sending HTTP headers in some PHP
101:             ob_start(function ($buffer) use ($handle) { fwrite($handle, $buffer); }, 4096);
102:             $this->renderTemplate($exception, __DIR__ . '/assets/BlueScreen/page.phtml', false);
103:             ob_end_flush();
104:             ob_end_clean();
105:             fclose($handle);
106:         }
107:     }
108: 
109: 
110:     private function renderTemplate($exception, $template, $toScreen = true)
111:     {
112:         $messageHtml = preg_replace(
113:             '#\'\S[^\']*\S\'|"\S[^"]*\S"#U',
114:             '<i>$0</i>',
115:             htmlspecialchars((string) $exception->getMessage(), ENT_SUBSTITUTE, 'UTF-8')
116:         );
117:         $info = array_filter($this->info);
118:         $source = Helpers::getSource();
119:         $sourceIsUrl = preg_match('#^https?://#', $source);
120:         $title = $exception instanceof \ErrorException
121:             ? Helpers::errorTypeToString($exception->getSeverity())
122:             : Helpers::getClass($exception);
123:         $lastError = $exception instanceof \ErrorException || $exception instanceof \Error ? null : error_get_last();
124: 
125:         $keysToHide = array_flip(array_map('strtolower', $this->keysToHide));
126:         $dump = function ($v, $k = null) use ($keysToHide) {
127:             if (is_string($k) && isset($keysToHide[strtolower($k)])) {
128:                 $v = Dumper::HIDDEN_VALUE;
129:             }
130:             return Dumper::toHtml($v, [
131:                 Dumper::DEPTH => $this->maxDepth,
132:                 Dumper::TRUNCATE => $this->maxLength,
133:                 Dumper::LIVE => true,
134:                 Dumper::LOCATION => Dumper::LOCATION_CLASS,
135:                 Dumper::KEYS_TO_HIDE => $this->keysToHide,
136:             ]);
137:         };
138:         $css = array_map('file_get_contents', array_merge([
139:             __DIR__ . '/assets/BlueScreen/bluescreen.css',
140:         ], Debugger::$customCssFiles));
141:         $css = preg_replace('#\s+#u', ' ', implode($css));
142: 
143:         $nonce = $toScreen ? Helpers::getNonce() : null;
144:         $actions = $toScreen ? $this->renderActions($exception) : [];
145: 
146:         require $template;
147:     }
148: 
149: 
150:     /**
151:      * @return \stdClass[]
152:      */
153:     private function renderPanels($ex)
154:     {
155:         $obLevel = ob_get_level();
156:         $res = [];
157:         foreach ($this->panels as $callback) {
158:             try {
159:                 $panel = call_user_func($callback, $ex);
160:                 if (empty($panel['tab']) || empty($panel['panel'])) {
161:                     continue;
162:                 }
163:                 $res[] = (object) $panel;
164:                 continue;
165:             } catch (\Exception $e) {
166:             } catch (\Throwable $e) {
167:             }
168:             while (ob_get_level() > $obLevel) { // restore ob-level if broken
169:                 ob_end_clean();
170:             }
171:             is_callable($callback, true, $name);
172:             $res[] = (object) [
173:                 'tab' => "Error in panel $name",
174:                 'panel' => nl2br(Helpers::escapeHtml($e)),
175:             ];
176:         }
177:         return $res;
178:     }
179: 
180: 
181:     /**
182:      * @return array[]
183:      */
184:     private function renderActions($ex)
185:     {
186:         $actions = [];
187:         foreach ($this->actions as $callback) {
188:             $action = call_user_func($callback, $ex);
189:             if (!empty($action['link']) && !empty($action['label'])) {
190:                 $actions[] = $action;
191:             }
192:         }
193: 
194:         if (property_exists($ex, 'tracyAction') && !empty($ex->tracyAction['link']) && !empty($ex->tracyAction['label'])) {
195:             $actions[] = $ex->tracyAction;
196:         }
197: 
198:         if (preg_match('# ([\'"])(\w{3,}(?:\\\\\w{3,})+)\\1#i', $ex->getMessage(), $m)) {
199:             $class = $m[2];
200:             if (
201:                 !class_exists($class) && !interface_exists($class) && !trait_exists($class)
202:                 && ($file = Helpers::guessClassFile($class)) && !is_file($file)
203:             ) {
204:                 $actions[] = [
205:                     'link' => Helpers::editorUri($file, 1, 'create'),
206:                     'label' => 'create class',
207:                 ];
208:             }
209:         }
210: 
211:         if (preg_match('# ([\'"])((?:/|[a-z]:[/\\\\])\w[^\'"]+\.\w{2,5})\\1#i', $ex->getMessage(), $m)) {
212:             $file = $m[2];
213:             $actions[] = [
214:                 'link' => Helpers::editorUri($file, 1, $label = is_file($file) ? 'open' : 'create'),
215:                 'label' => $label . ' file',
216:             ];
217:         }
218: 
219:         $query = ($ex instanceof \ErrorException ? '' : Helpers::getClass($ex) . ' ')
220:             . preg_replace('#\'.*\'|".*"#Us', '', $ex->getMessage());
221:         $actions[] = [
222:             'link' => 'https://www.google.com/search?sourceid=tracy&q=' . urlencode($query),
223:             'label' => 'search',
224:             'external' => true,
225:         ];
226: 
227:         if (
228:             $ex instanceof \ErrorException
229:             && !empty($ex->skippable)
230:             && preg_match('#^https?://#', $source = Helpers::getSource())
231:         ) {
232:             $actions[] = [
233:                 'link' => $source . (strpos($source, '?') ? '&' : '?') . '_tracy_skip_error',
234:                 'label' => 'skip error',
235:             ];
236:         }
237:         return $actions;
238:     }
239: 
240: 
241:     /**
242:      * Returns syntax highlighted source code.
243:      * @param  string  $file
244:      * @param  int  $line
245:      * @param  int  $lines
246:      * @return string|null
247:      */
248:     public static function highlightFile($file, $line, $lines = 15, array $vars = null)
249:     {
250:         $source = @file_get_contents($file); // @ file may not exist
251:         if ($source) {
252:             $source = static::highlightPhp($source, $line, $lines, $vars);
253:             if ($editor = Helpers::editorUri($file, $line)) {
254:                 $source = substr_replace($source, ' data-tracy-href="' . Helpers::escapeHtml($editor) . '"', 4, 0);
255:             }
256:             return $source;
257:         }
258:     }
259: 
260: 
261:     /**
262:      * Returns syntax highlighted source code.
263:      * @param  string  $source
264:      * @param  int  $line
265:      * @param  int  $lines
266:      * @return string
267:      */
268:     public static function highlightPhp($source, $line, $lines = 15, array $vars = null)
269:     {
270:         if (function_exists('ini_set')) {
271:             ini_set('highlight.comment', '#998; font-style: italic');
272:             ini_set('highlight.default', '#000');
273:             ini_set('highlight.html', '#06B');
274:             ini_set('highlight.keyword', '#D24; font-weight: bold');
275:             ini_set('highlight.string', '#080');
276:         }
277: 
278:         $source = str_replace(["\r\n", "\r"], "\n", $source);
279:         $source = explode("\n", highlight_string($source, true));
280:         $out = $source[0]; // <code><span color=highlight.html>
281:         $source = str_replace('<br />', "\n", $source[1]);
282:         $out .= static::highlightLine($source, $line, $lines);
283: 
284:         if ($vars) {
285:             $out = preg_replace_callback('#">\$(\w+)(&nbsp;)?</span>#', function ($m) use ($vars) {
286:                 return array_key_exists($m[1], $vars)
287:                     ? '" title="'
288:                         . str_replace('"', '&quot;', trim(strip_tags(Dumper::toHtml($vars[$m[1]], [Dumper::DEPTH => 1]))))
289:                         . $m[0]
290:                     : $m[0];
291:             }, $out);
292:         }
293: 
294:         $out = str_replace('&nbsp;', ' ', $out);
295:         return "<pre class='code'><div>$out</div></pre>";
296:     }
297: 
298: 
299:     /**
300:      * Returns highlighted line in HTML code.
301:      * @return string
302:      */
303:     public static function highlightLine($html, $line, $lines = 15)
304:     {
305:         $source = explode("\n", "\n" . str_replace("\r\n", "\n", $html));
306:         $out = '';
307:         $spans = 1;
308:         $start = $i = max(1, min($line, count($source) - 1) - (int) floor($lines * 2 / 3));
309:         while (--$i >= 1) { // find last highlighted block
310:             if (preg_match('#.*(</?span[^>]*>)#', $source[$i], $m)) {
311:                 if ($m[1] !== '</span>') {
312:                     $spans++;
313:                     $out .= $m[1];
314:                 }
315:                 break;
316:             }
317:         }
318: 
319:         $source = array_slice($source, $start, $lines, true);
320:         end($source);
321:         $numWidth = strlen((string) key($source));
322: 
323:         foreach ($source as $n => $s) {
324:             $spans += substr_count($s, '<span') - substr_count($s, '</span');
325:             $s = str_replace(["\r", "\n"], ['', ''], $s);
326:             preg_match_all('#<[^>]+>#', $s, $tags);
327:             if ($n == $line) {
328:                 $out .= sprintf(
329:                     "<span class='highlight'>%{$numWidth}s:    %s\n</span>%s",
330:                     $n,
331:                     strip_tags($s),
332:                     implode('', $tags[0])
333:                 );
334:             } else {
335:                 $out .= sprintf("<span class='line'>%{$numWidth}s:</span>    %s\n", $n, $s);
336:             }
337:         }
338:         $out .= str_repeat('</span>', $spans) . '</code>';
339:         return $out;
340:     }
341: 
342: 
343:     /**
344:      * Should a file be collapsed in stack trace?
345:      * @param  string  $file
346:      * @return bool
347:      */
348:     public function isCollapsed($file)
349:     {
350:         $file = strtr($file, '\\', '/') . '/';
351:         foreach ($this->collapsePaths as $path) {
352:             $path = strtr($path, '\\', '/') . '/';
353:             if (strncmp($file, $path, strlen($path)) === 0) {
354:                 return true;
355:             }
356:         }
357:         return false;
358:     }
359: }
360: 
Nette 2.4-20191120 API API documentation generated by ApiGen 2.8.0