feature #32661 [ErrorRenderer] Improving the exception page provided by HtmlErrorRenderer (yceruto)

This PR was squashed before being merged into the 4.4 branch (closes #32661).

Discussion
----------

[ErrorRenderer] Improving the exception page provided by HtmlErrorRenderer

| Q             | A
| ------------- | ---
| Branch?       | 4.4
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

As part of the component responsability, this proposal is intended to provide a full exception page (advanced) in debug mode, just like the one provided by TwigBundle which would be deprecated.

Commits
-------

5d76eb7b86 [ErrorRenderer] Improving the exception page provided by HtmlErrorRenderer
This commit is contained in:
Fabien Potencier 2019-07-23 08:35:18 +02:00
commit 12f99c09c4
25 changed files with 1188 additions and 221 deletions

View File

@ -14,6 +14,9 @@
<argument>%kernel.debug%</argument>
<argument>%kernel.charset%</argument>
<argument>%debug.file_link_format%</argument>
<argument>%kernel.project_dir%</argument>
<argument type="service" id="request_stack" />
<argument type="service" id="logger" on-invalid="null" />
</service>
<service id="error_renderer.renderer.json" class="Symfony\Component\ErrorRenderer\ErrorRenderer\JsonErrorRenderer">

View File

@ -52,16 +52,15 @@ class ExceptionHandlerTest extends TestCase
$response = ob_get_clean();
$this->assertContains('<h1 class="break-long-words exception-message">Foo</h1>', $response);
$this->assertContains('<div class="trace trace-as-html">', $response);
$this->assertContains('<div class="trace trace-as-html" id="trace-box-1">', $response);
// taken from https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)
$htmlWithXss = '<body onload=alert(\'test1\')> <b onmouseover=alert(\'Wufff!\')>click me!</b> <img src="j&#X41vascript:alert(\'test2\')"> <meta http-equiv="refresh"
content="0;url=data:text/html;base64,PHNjcmlwdD5hbGVydCgndGVzdDMnKTwvc2NyaXB0Pg">';
$htmlWithXss = '<body onload=alert(\'test1\')> <b onmouseover=alert(\'Wufff!\')>click me!</b> <img src="j&#X41vascript:alert(\'test2\')"> <meta http-equiv="refresh" content="0;url=data:text/html;base64,PHNjcmlwdD5hbGVydCgndGVzdDMnKTwvc2NyaXB0Pg">';
ob_start();
$handler->sendPhpResponse(new \RuntimeException($htmlWithXss));
$response = ob_get_clean();
$this->assertContains(sprintf('<h1 class="break-long-words exception-message">%s</h1>', htmlspecialchars($htmlWithXss, ENT_COMPAT | ENT_SUBSTITUTE, 'UTF-8')), $response);
$this->assertContains(sprintf('<h1 class="break-long-words exception-message long">%s</h1>', htmlspecialchars($htmlWithXss, ENT_COMPAT | ENT_SUBSTITUTE, 'UTF-8')), $response);
}
public function testStatusCode()
@ -106,7 +105,7 @@ content="0;url=data:text/html;base64,PHNjcmlwdD5hbGVydCgndGVzdDMnKTwvc2NyaXB0Pg"
$handler->sendPhpResponse(new \RuntimeException('Foo', 0, new \RuntimeException('Bar')));
$response = ob_get_clean();
$this->assertStringMatchesFormat('%A<p class="break-long-words trace-message">Foo</p>%A<p class="break-long-words trace-message">Bar</p>%A', $response);
$this->assertStringMatchesFormat('%A<h1 class="break-long-words exception-message">Foo</h1>%A<p class="break-long-words trace-message">Bar</p>%A', $response);
}
public function testHandle()

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,4 @@
body { background-color: #fff; color: #222; font: 16px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; margin: 0; }
.container { margin: 30px; max-width: 600px; }
h1 { color: #dc3545; font-size: 24px; }
h2 { font-size: 18px; }

View File

@ -0,0 +1,209 @@
/* This file is based on WebProfilerBundle/Resources/views/Profiler/profiler.css.twig.
If you make any change in this file, verify the same change is needed in the other file. */
:root {
--font-sans-serif: Helvetica, Arial, sans-serif;
--page-background: #f9f9f9;
--color-text: #222;
/* when updating any of these colors, do the same in toolbar.css.twig */
--color-success: #4f805d;
--color-warning: #a46a1f;
--color-error: #b0413e;
--color-muted: #999;
--tab-background: #fff;
--tab-color: #444;
--tab-active-background: #666;
--tab-active-color: #fafafa;
--tab-disabled-background: #f5f5f5;
--tab-disabled-color: #999;
--metric-value-background: #fff;
--metric-value-color: inherit;
--metric-unit-color: #999;
--metric-label-background: #e0e0e0;
--metric-label-color: inherit;
--table-border: #e0e0e0;
--table-background: #fff;
--table-header: #e0e0e0;
--trace-selected-background: #F7E5A1;
--tree-active-background: #F7E5A1;
--exception-title-color: var(--base-2);
--shadow: 0px 0px 1px rgba(128, 128, 128, .2);
--border: 1px solid #e0e0e0;
--background-error: var(--color-error);
--highlight-comment: #969896;
--highlight-default: #222222;
--highlight-keyword: #a71d5d;
--highlight-string: #183691;
--base-0: #fff;
--base-1: #f5f5f5;
--base-2: #e0e0e0;
--base-3: #ccc;
--base-4: #666;
--base-5: #444;
--base-6: #222;
}
html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}
html {
/* always display the vertical scrollbar to avoid jumps when toggling contents */
overflow-y: scroll;
}
body { background-color: #F9F9F9; color: var(--base-6); font: 14px/1.4 Helvetica, Arial, sans-serif; padding-bottom: 45px; }
a { cursor: pointer; text-decoration: none; }
a:hover { text-decoration: underline; }
abbr[title] { border-bottom: none; cursor: help; text-decoration: none; }
code, pre { font: 13px/1.5 Consolas, Monaco, Menlo, "Ubuntu Mono", "Liberation Mono", monospace; }
table, tr, th, td { background: #FFF; border-collapse: collapse; vertical-align: top; }
table { background: #FFF; border: var(--border); box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; width: 100%; }
table th, table td { border: solid var(--base-2); border-width: 1px 0; padding: 8px 10px; }
table th { background-color: var(--base-2); font-weight: bold; text-align: left; }
.m-t-5 { margin-top: 5px; }
.hidden-xs-down { display: none; }
.block { display: block; }
.full-width { width: 100%; }
.hidden { display: none; }
.prewrap { white-space: pre-wrap; }
.nowrap { white-space: nowrap; }
.newline { display: block; }
.break-long-words { word-wrap: break-word; overflow-wrap: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; min-width: 0; }
.text-small { font-size: 12px !important; }
.text-muted { color: #999; }
.text-bold { font-weight: bold; }
.empty { border: 4px dashed var(--base-2); color: #999; margin: 1em 0; padding: .5em 2em; }
.status-success { background: rgba(94, 151, 110, 0.3); }
.status-warning { background: rgba(240, 181, 24, 0.3); }
.status-error { background: rgba(176, 65, 62, 0.2); }
.status-success td, .status-warning td, .status-error td { background: transparent; }
tr.status-error td, tr.status-warning td { border-bottom: 1px solid #FAFAFA; border-top: 1px solid #FAFAFA; }
.status-warning .colored { color: #A46A1F; }
.status-error .colored { color: var(--color-error); }
.sf-toggle { cursor: pointer; }
.sf-toggle-content { -moz-transition: display .25s ease; -webkit-transition: display .25s ease; transition: display .25s ease; }
.sf-toggle-content.sf-toggle-hidden { display: none; }
.sf-toggle-content.sf-toggle-visible { display: block; }
thead.sf-toggle-content.sf-toggle-visible, tbody.sf-toggle-content.sf-toggle-visible { display: table-row-group; }
.sf-toggle-off .icon-close, .sf-toggle-on .icon-open { display: none; }
.sf-toggle-off .icon-open, .sf-toggle-on .icon-close { display: block; }
.tab-navigation { margin: 0 0 1em 0; padding: 0; }
.tab-navigation li { background: var(--tab-background); border: 1px solid var(--table-border); color: var(--tab-color); cursor: pointer; display: inline-block; font-size: 16px; margin: 0 0 0 -1px; padding: .5em .75em; z-index: 1; }
.tab-navigation li .badge { background-color: var(--base-1); color: var(--base-4); display: inline-block; font-size: 14px; font-weight: bold; margin-left: 8px; min-width: 10px; padding: 1px 6px; text-align: center; white-space: nowrap; }
.tab-navigation li.disabled { background: var(--tab-disabled-background); color: var(--tab-disabled-color); }
.tab-navigation li.active { background: var(--tab-active-background); color: var(--tab-active-color); z-index: 1100; }
.tab-navigation li.active .badge { background-color: var(--base-5); color: var(--base-2); }
.tab-content > *:first-child { margin-top: 0; }
.tab-navigation li .badge.status-warning { background: var(--color-warning); color: #FFF; }
.tab-navigation li .badge.status-error { background: var(--background-error); color: #FFF; }
.sf-tabs .tab:not(:first-child) { display: none; }
[data-filters] { position: relative; }
[data-filtered] { cursor: pointer; }
[data-filtered]:after { content: '\00a0\25BE'; }
[data-filtered]:hover .filter-list li { display: inline-flex; }
[class*="filter-hidden-"] { display: none; }
.filter-list { position: absolute; border: var(--border); box-shadow: var(--shadow); margin: 0; padding: 0; display: flex; flex-direction: column; }
.filter-list :after { content: ''; }
.filter-list li {
background: var(--tab-disabled-background);
border-bottom: var(--border);
color: var(--tab-disabled-color);
display: none;
list-style: none;
margin: 0;
padding: 5px 10px;
text-align: left;
font-weight: normal;
}
.filter-list li.active {
background: var(--tab-background);
color: var(--tab-color);
}
.filter-list li.last-active {
background: var(--tab-active-background);
color: var(--tab-active-color);
}
.filter-list-level li { cursor: s-resize; }
.filter-list-level li.active { cursor: n-resize; }
.filter-list-level li.last-active { cursor: default; }
.filter-list-level li.last-active:before { content: '\2714\00a0'; }
.filter-list-choice li:before { content: '\2714\00a0'; color: transparent; }
.filter-list-choice li.active:before { color: unset; }
.container { max-width: 1024px; margin: 0 auto; padding: 0 15px; }
.container::after { content: ""; display: table; clear: both; }
header { background-color: var(--base-6); color: rgba(255, 255, 255, 0.75); font-size: 13px; height: 33px; line-height: 33px; padding: 0; }
header .container { display: flex; justify-content: space-between; }
.logo { flex: 1; font-size: 13px; font-weight: normal; margin: 0; padding: 0; }
.logo svg { height: 18px; width: 18px; opacity: .8; vertical-align: -5px; }
.help-link { margin-left: 15px; }
.help-link a { color: inherit; }
.help-link .icon svg { height: 15px; width: 15px; opacity: .7; vertical-align: -2px; }
.help-link a:hover { color: #EEE; text-decoration: none; }
.help-link a:hover svg { opacity: .9; }
.exception-summary { background: var(--background-error); border-bottom: 2px solid rgba(0, 0, 0, 0.1); border-top: 1px solid rgba(0, 0, 0, .3); flex: 0 0 auto; margin-bottom: 15px; }
.exception-metadata { background: rgba(0, 0, 0, 0.1); padding: 7px 0; }
.exception-metadata .container { display: flex; flex-direction: row; justify-content: space-between; }
.exception-metadata h2, .exception-metadata h2 > a { color: rgba(255, 255, 255, 0.8); font-size: 13px; font-weight: 400; margin: 0; }
.exception-http small { font-size: 13px; opacity: .7; }
.exception-hierarchy { flex: 1; }
.exception-hierarchy .icon { margin: 0 3px; opacity: .7; }
.exception-hierarchy .icon svg { height: 13px; width: 13px; vertical-align: -2px; }
.exception-without-message .exception-message-wrapper { display: none; }
.exception-message-wrapper .container { display: flex; align-items: flex-start; min-height: 70px; padding: 10px 15px 8px; }
.exception-message { flex-grow: 1; }
.exception-message, .exception-message a { color: #FFF; font-size: 21px; font-weight: 400; margin: 0; }
.exception-message.long { font-size: 18px; }
.exception-message a { border-bottom: 1px solid rgba(255, 255, 255, 0.5); font-size: inherit; text-decoration: none; }
.exception-message a:hover { border-bottom-color: #ffffff; }
.exception-illustration { flex-basis: 111px; flex-shrink: 0; height: 66px; margin-left: 15px; opacity: .7; }
.trace + .trace { margin-top: 30px; }
.trace-head { background-color: var(--base-2); padding: 10px; position: relative; }
.trace-head .trace-class { color: var(--base-6); font-size: 18px; font-weight: bold; line-height: 1.3; margin: 0; position: relative; }
.trace-head .trace-namespace { color: #999; display: block; font-size: 13px; }
.trace-head .icon { position: absolute; right: 0; top: 0; }
.trace-head .icon svg { height: 24px; width: 24px; }
.trace-details { background: var(--base-0); border: var(--border); box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; table-layout: fixed; }
.trace-message { font-size: 14px; font-weight: normal; margin: .5em 0 0; }
.trace-line { position: relative; padding-top: 8px; padding-bottom: 8px; }
.trace-line + .trace-line { border-top: var(--border); }
.trace-line:hover { background: var(--base-1); }
.trace-line a { color: var(--base-6); }
.trace-line .icon { opacity: .4; position: absolute; left: 10px; top: 11px; }
.trace-line .icon svg { height: 16px; width: 16px; }
.trace-line-header { padding-left: 36px; padding-right: 10px; }
.trace-file-path, .trace-file-path a { color: var(--base-6); font-size: 13px; }
.trace-class { color: var(--color-error); }
.trace-type { padding: 0 2px; }
.trace-method { color: var(--color-error); font-weight: bold; }
.trace-arguments { color: #777; font-weight: normal; padding-left: 2px; }
.trace-code { background: var(--base-0); font-size: 12px; margin: 10px 10px 2px 10px; padding: 10px; overflow-x: auto; white-space: nowrap; }
.trace-code ol { margin: 0; float: left; }
.trace-code li { color: #969896; margin: 0; padding-left: 10px; float: left; width: 100%; }
.trace-code li + li { margin-top: 5px; }
.trace-code li.selected { background: var(--trace-selected-background); margin-top: 2px; }
.trace-code li code { color: var(--base-6); white-space: nowrap; }
.trace-as-text .stacktrace { line-height: 1.8; margin: 0 0 15px; white-space: pre-wrap; }
@media (min-width: 575px) {
.hidden-xs-down { display: initial; }
.help-link { margin-left: 30px; }
}

View File

@ -0,0 +1,128 @@
.sf-reset .traces {
padding-bottom: 14px;
}
.sf-reset .traces li {
font-size: 12px;
color: #868686;
padding: 5px 4px;
list-style-type: decimal;
margin-left: 20px;
}
.sf-reset #logs .traces li.error {
font-style: normal;
color: #AA3333;
background: #f9ecec;
}
.sf-reset #logs .traces li.warning {
font-style: normal;
background: #ffcc00;
}
/* fix for Opera not liking empty <li> */
.sf-reset .traces li:after {
content: "\00A0";
}
.sf-reset .trace {
border: 1px solid #D3D3D3;
padding: 10px;
overflow: auto;
margin: 10px 0 20px;
}
.sf-reset .block-exception {
-moz-border-radius: 16px;
-webkit-border-radius: 16px;
border-radius: 16px;
margin-bottom: 20px;
background-color: #f6f6f6;
border: 1px solid #dfdfdf;
padding: 30px 28px;
word-wrap: break-word;
overflow: hidden;
}
.sf-reset .block-exception div {
color: #313131;
font-size: 10px;
}
.sf-reset .block-exception-detected .illustration-exception,
.sf-reset .block-exception-detected .text-exception {
float: left;
}
.sf-reset .block-exception-detected .illustration-exception {
width: 152px;
}
.sf-reset .block-exception-detected .text-exception {
width: 670px;
padding: 30px 44px 24px 46px;
position: relative;
}
.sf-reset .text-exception .open-quote,
.sf-reset .text-exception .close-quote {
font-family: Arial, Helvetica, sans-serif;
position: absolute;
color: #C9C9C9;
font-size: 8em;
}
.sf-reset .open-quote {
top: 0;
left: 0;
}
.sf-reset .close-quote {
bottom: -0.5em;
right: 50px;
}
.sf-reset .block-exception p {
font-family: Arial, Helvetica, sans-serif;
}
.sf-reset .block-exception p a,
.sf-reset .block-exception p a:hover {
color: #565656;
}
.sf-reset .logs h2 {
float: left;
width: 654px;
}
.sf-reset .error-count, .sf-reset .support {
float: right;
width: 170px;
text-align: right;
}
.sf-reset .error-count span {
display: inline-block;
background-color: #aacd4e;
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
border-radius: 6px;
padding: 4px;
color: white;
margin-right: 2px;
font-size: 11px;
font-weight: bold;
}
.sf-reset .support a {
display: inline-block;
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
border-radius: 6px;
padding: 4px;
color: #000000;
margin-right: 2px;
font-size: 11px;
font-weight: bold;
}
.sf-reset .toggle {
vertical-align: middle;
}
.sf-reset .linked ul,
.sf-reset .linked li {
display: inline;
}
.sf-reset #output-content {
color: #000;
font-size: 12px;
}
.sf-reset #traces-text pre {
white-space: pre;
font-size: 12px;
font-family: monospace;
}

View File

@ -0,0 +1 @@
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path fill="#FFF" d="M1363 877l-742 742q-19 19-45 19t-45-19l-166-166q-19-19-19-45t19-45l531-531-531-531q-19-19-19-45t19-45L531 45q19-19 45-19t45 19l742 742q19 19 19 45t-19 45z"/></svg>

After

Width:  |  Height:  |  Size: 276 B

View File

@ -0,0 +1 @@
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAgCAYAAAABtRhCAAADVUlEQVRIx82XX0jTURTHLYPyqZdefQx66CEo80+aYpoIkqzUikz6Z5klQoWUWYRIJYEUGpQ+lIr9U5dOTLdCtkmWZis3rbnC5fw/neYW002307mX/cZvP3/7o1PwwOdh95x7vnf39zvnd29AgBer2xO6DclAXiMqZAqxIiNIN/IYSUS2BPhjmGATchUxI+ADWiRhpWK7HKuHFVBFdmU5YvnI4grFGCaReF/EBH4KsZlGgj2JBTuCYBWRIYF8YoEOJ6wBt/gEs7mBbyOjQXruPLSdOgPCiEiPSUUHDoL8Ug5IUo9B/d5wrt+G7OAKNrODPuVdB6vRCIzN6SdBlpW9RIgk/1FeAXabzRlrUPVCS/JhbmwudztnGeeH9AyXBIwtmM3wLinZJZHifjHw2V+NBoRh+9ixQrbgbnaSIcl7cGea6hoXQbNe7za241oeO5Z0p42M4BV2EqP2D50wo+6HzvwC6C4sApNOR8cmOrtcnhtj2kYRyC9eBvXzKrBZrXSs72kFd1t3MoKVbMekQkEnSNKOO8fac3LpmK6l1TlGtsxmsdKFsecPYgwxst0cwROMYDXboSotg0WLBRqjY51jLYcENElXwW2XJKPydvoI2GN9T8rBtrAArYIUruBJXkFheCQYlCpQP6uk5dAQFQNaUROMSGVQFxLmkoQsxDJrhLbTZ+nvVsERME9MgPJRKV/58AsyomTSzE813WLFvWK++qI0xSfQl8k8Pg46sYRuv5t6dS+4RqxDwaa4BGjYH+NTQvKScIp9+YL/hoZh3jDtLRHtt2C3g6bmhX+CpsFBWg7ilDSPgj0lD2ncr5ev/BP8VvyAJhqVyZeUhPOrEhEFxgEtjft846Z/guQTNT89Q5P9flMLoth4F7808wKtWWKzAwNQHxrh/1vaid2F+XpYTSbQf1XA2McOmOpROnvpvMEA4tSjq1cW0sws2gCYxswY6TKkvzYnJq1NHZLnRU4BX+4U0uburvusu8Kv8iHY7qefkM4IFngJHEOUXmLEPgiGsI8YnlZILit3vSSLRTQe/MPIZva5pshNIEmyFQlCvruJKXPkCEfmePzkphXHdzZNQdoRI9KPlBAxlj/I8U97ERPS5bjGbWDFbEdqHVe5caTBeZZx2H/IMvzeN15yoQAAAABJRU5ErkJggg==

View File

@ -0,0 +1 @@
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path fill="#FFF" d="M1703 478q40 57 18 129l-275 906q-19 64-76.5 107.5T1247 1664H324q-77 0-148.5-53.5T76 1479q-24-67-2-127 0-4 3-27t4-37q1-8-3-21.5t-3-19.5q2-11 8-21t16.5-23.5T116 1179q23-38 45-91.5t30-91.5q3-10 .5-30t-.5-28q3-11 17-28t17-23q21-36 42-92t25-90q1-9-2.5-32t.5-28q4-13 22-30.5t22-22.5q19-26 42.5-84.5T404 411q1-8-3-25.5t-2-26.5q2-8 9-18t18-23 17-21q8-12 16.5-30.5t15-35 16-36 19.5-32 26.5-23.5 36-11.5T620 134l-1 3q38-9 51-9h761q74 0 114 56t18 130l-274 906q-36 119-71.5 153.5T1089 1408H220q-27 0-38 15-11 16-1 43 24 70 144 70h923q29 0 56-15.5t35-41.5l300-987q7-22 5-57 38 15 59 43zm-1064 2q-4 13 2 22.5t20 9.5h608q13 0 25.5-9.5T1311 480l21-64q4-13-2-22.5t-20-9.5H702q-13 0-25.5 9.5T660 416zm-83 256q-4 13 2 22.5t20 9.5h608q13 0 25.5-9.5T1228 736l21-64q4-13-2-22.5t-20-9.5H619q-13 0-25.5 9.5T577 672z"/></svg>

After

Width:  |  Height:  |  Size: 913 B

View File

@ -0,0 +1 @@
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1344 800v64q0 14-9 23t-23 9H480q-14 0-23-9t-9-23v-64q0-14 9-23t23-9h832q14 0 23 9t9 23zm128 448V416q0-66-47-113t-113-47H480q-66 0-113 47t-47 113v832q0 66 47 113t113 47h832q66 0 113-47t47-113zm128-832v832q0 119-84.5 203.5T1312 1536H480q-119 0-203.5-84.5T192 1248V416q0-119 84.5-203.5T480 128h832q119 0 203.5 84.5T1600 416z"/></svg>

After

Width:  |  Height:  |  Size: 432 B

View File

@ -0,0 +1 @@
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1408 960V832q0-26-19-45t-45-19H448q-26 0-45 19t-19 45v128q0 26 19 45t45 19h896q26 0 45-19t19-45zm256-544v960q0 119-84.5 203.5T1376 1664H416q-119 0-203.5-84.5T128 1376V416q0-119 84.5-203.5T416 128h960q119 0 203.5 84.5T1664 416z"/></svg>

After

Width:  |  Height:  |  Size: 337 B

View File

@ -0,0 +1 @@
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1344 800v64q0 14-9 23t-23 9H960v352q0 14-9 23t-23 9h-64q-14 0-23-9t-9-23V896H480q-14 0-23-9t-9-23v-64q0-14 9-23t23-9h352V416q0-14 9-23t23-9h64q14 0 23 9t9 23v352h352q14 0 23 9t9 23zm128 448V416q0-66-47-113t-113-47H480q-66 0-113 47t-47 113v832q0 66 47 113t113 47h832q66 0 113-47t47-113zm128-832v832q0 119-84.5 203.5T1312 1536H480q-119 0-203.5-84.5T192 1248V416q0-119 84.5-203.5T480 128h832q119 0 203.5 84.5T1600 416z"/></svg>

After

Width:  |  Height:  |  Size: 526 B

View File

@ -0,0 +1 @@
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1408 960V832q0-26-19-45t-45-19h-320V448q0-26-19-45t-45-19H832q-26 0-45 19t-19 45v320H448q-26 0-45 19t-19 45v128q0 26 19 45t45 19h320v320q0 26 19 45t45 19h128q26 0 45-19t19-45v-320h320q26 0 45-19t19-45zm256-544v960q0 119-84.5 203.5T1376 1664H416q-119 0-203.5-84.5T128 1376V416q0-119 84.5-203.5T416 128h960q119 0 203.5 84.5T1664 416z"/></svg>

After

Width:  |  Height:  |  Size: 442 B

View File

@ -0,0 +1 @@
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path fill="#FFF" d="M896 0q182 0 348 71t286 191 191 286 71 348-71 348-191 286-286 191-348 71-348-71-286-191-191-286T0 896t71-348 191-286T548 71 896 0zm0 128q-190 0-361 90l194 194q82-28 167-28t167 28l194-194q-171-90-361-90zM218 1257l194-194q-28-82-28-167t28-167L218 535q-90 171-90 361t90 361zm678 407q190 0 361-90l-194-194q-82 28-167 28t-167-28l-194 194q171 90 361 90zm0-384q159 0 271.5-112.5T1280 896t-112.5-271.5T896 512 624.5 624.5 512 896t112.5 271.5T896 1280zm484-217l194 194q90-171 90-361t-90-361l-194 194q28 82 28 167t-28 167z"/></svg>

After

Width:  |  Height:  |  Size: 634 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#FFF" d="M12 .9C5.8.9.9 5.8.9 12a11 11 0 1 0 22.2 0A11 11 0 0 0 12 .9zm6.5 6c-.6 0-.9-.3-.9-.8 0-.2 0-.4.2-.6l.2-.4c0-.3-.5-.4-.6-.4-1.8.1-2.3 2.5-2.7 4.4l-.2 1c1 .2 1.8 0 2.2-.3.6-.4-.2-.7-.1-1.2.1-.3.5-.5.7-.6.5 0 .7.5.7.9 0 .7-1 1.8-3 1.8l-.6-.1-.6 2.4c-.4 1.6-.8 3.8-2.4 5.7-1.4 1.7-2.9 1.9-3.5 1.9-1.2 0-1.9-.6-2-1.5 0-.8.7-1.3 1.2-1.3.6 0 1.1.5 1.1 1s-.2.6-.4.6c-.1.1-.3.2-.3.4 0 .1.1.3.4.3.5 0 .8-.3 1.1-.5 1.2-.9 1.6-2.7 2.2-5.7l.1-.7.7-3.2c-.8-.6-1.3-1.4-2.4-1.7-.6-.1-1.1.1-1.5.5-.4.5-.2 1.1.2 1.5l.7.6c.7.8 1.2 1.6 1 2.5-.3 1.5-2 2.6-4 1.9-1.8-.6-2-1.8-1.8-2.5.2-.6.6-.7 1.1-.6.5.2.6.7.6 1.2l-.1.3c-.2.1-.3.3-.3.4-.1.4.4.6.7.7.7.3 1.6-.2 1.8-.8a1 1 0 0 0-.4-1.1l-.7-.8c-.4-.4-1.1-1.4-.7-2.6.1-.5.4-.9.7-1.3a4 4 0 0 1 2.8-.6c1.2.4 1.8 1.1 2.6 1.8.5-1.2 1-2.4 1.8-3.5.9-.9 1.9-1.6 3.1-1.7 1.3.2 2.2.7 2.2 1.6 0 .4-.2 1.1-.9 1.1z"/></svg>

After

Width:  |  Height:  |  Size: 942 B

View File

@ -0,0 +1,279 @@
/* This file is based on WebProfilerBundle/Resources/views/Profiler/base_js.html.twig.
If you make any change in this file, verify the same change is needed in the other file. */
/*<![CDATA[*/
Sfjs = (function() {
"use strict";
if ('classList' in document.documentElement) {
var hasClass = function (el, cssClass) { return el.classList.contains(cssClass); };
var removeClass = function(el, cssClass) { el.classList.remove(cssClass); };
var addClass = function(el, cssClass) { el.classList.add(cssClass); };
var toggleClass = function(el, cssClass) { el.classList.toggle(cssClass); };
} else {
var hasClass = function (el, cssClass) { return el.className.match(new RegExp('\\b' + cssClass + '\\b')); };
var removeClass = function(el, cssClass) { el.className = el.className.replace(new RegExp('\\b' + cssClass + '\\b'), ' '); };
var addClass = function(el, cssClass) { if (!hasClass(el, cssClass)) { el.className += " " + cssClass; } };
var toggleClass = function(el, cssClass) { hasClass(el, cssClass) ? removeClass(el, cssClass) : addClass(el, cssClass); };
}
var addEventListener;
var el = document.createElement('div');
if (!('addEventListener' in el)) {
addEventListener = function (element, eventName, callback) {
element.attachEvent('on' + eventName, callback);
};
} else {
addEventListener = function (element, eventName, callback) {
element.addEventListener(eventName, callback, false);
};
}
return {
addEventListener: addEventListener,
createTabs: function() {
var tabGroups = document.querySelectorAll('.sf-tabs:not([data-processed=true])');
/* create the tab navigation for each group of tabs */
for (var i = 0; i < tabGroups.length; i++) {
var tabs = tabGroups[i].querySelectorAll(':scope > .tab');
var tabNavigation = document.createElement('ul');
tabNavigation.className = 'tab-navigation';
var selectedTabId = 'tab-' + i + '-0'; /* select the first tab by default */
for (var j = 0; j < tabs.length; j++) {
var tabId = 'tab-' + i + '-' + j;
var tabTitle = tabs[j].querySelector('.tab-title').innerHTML;
var tabNavigationItem = document.createElement('li');
tabNavigationItem.setAttribute('data-tab-id', tabId);
if (hasClass(tabs[j], 'active')) { selectedTabId = tabId; }
if (hasClass(tabs[j], 'disabled')) { addClass(tabNavigationItem, 'disabled'); }
tabNavigationItem.innerHTML = tabTitle;
tabNavigation.appendChild(tabNavigationItem);
var tabContent = tabs[j].querySelector('.tab-content');
tabContent.parentElement.setAttribute('id', tabId);
}
tabGroups[i].insertBefore(tabNavigation, tabGroups[i].firstChild);
addClass(document.querySelector('[data-tab-id="' + selectedTabId + '"]'), 'active');
}
/* display the active tab and add the 'click' event listeners */
for (i = 0; i < tabGroups.length; i++) {
tabNavigation = tabGroups[i].querySelectorAll(':scope >.tab-navigation li');
for (j = 0; j < tabNavigation.length; j++) {
tabId = tabNavigation[j].getAttribute('data-tab-id');
document.getElementById(tabId).querySelector('.tab-title').className = 'hidden';
if (hasClass(tabNavigation[j], 'active')) {
document.getElementById(tabId).className = 'block';
} else {
document.getElementById(tabId).className = 'hidden';
}
tabNavigation[j].addEventListener('click', function(e) {
var activeTab = e.target || e.srcElement;
/* needed because when the tab contains HTML contents, user can click */
/* on any of those elements instead of their parent '<li>' element */
while (activeTab.tagName.toLowerCase() !== 'li') {
activeTab = activeTab.parentNode;
}
/* get the full list of tabs through the parent of the active tab element */
var tabNavigation = activeTab.parentNode.children;
for (var k = 0; k < tabNavigation.length; k++) {
var tabId = tabNavigation[k].getAttribute('data-tab-id');
document.getElementById(tabId).className = 'hidden';
removeClass(tabNavigation[k], 'active');
}
addClass(activeTab, 'active');
var activeTabId = activeTab.getAttribute('data-tab-id');
document.getElementById(activeTabId).className = 'block';
});
}
tabGroups[i].setAttribute('data-processed', 'true');
}
},
createToggles: function() {
var toggles = document.querySelectorAll('.sf-toggle:not([data-processed=true])');
for (var i = 0; i < toggles.length; i++) {
var elementSelector = toggles[i].getAttribute('data-toggle-selector');
var element = document.querySelector(elementSelector);
addClass(element, 'sf-toggle-content');
if (toggles[i].hasAttribute('data-toggle-initial') && toggles[i].getAttribute('data-toggle-initial') == 'display') {
addClass(toggles[i], 'sf-toggle-on');
addClass(element, 'sf-toggle-visible');
} else {
addClass(toggles[i], 'sf-toggle-off');
addClass(element, 'sf-toggle-hidden');
}
addEventListener(toggles[i], 'click', function(e) {
e.preventDefault();
if ('' !== window.getSelection().toString()) {
/* Don't do anything on text selection */
return;
}
var toggle = e.target || e.srcElement;
/* needed because when the toggle contains HTML contents, user can click */
/* on any of those elements instead of their parent '.sf-toggle' element */
while (!hasClass(toggle, 'sf-toggle')) {
toggle = toggle.parentNode;
}
var element = document.querySelector(toggle.getAttribute('data-toggle-selector'));
toggleClass(toggle, 'sf-toggle-on');
toggleClass(toggle, 'sf-toggle-off');
toggleClass(element, 'sf-toggle-hidden');
toggleClass(element, 'sf-toggle-visible');
/* the toggle doesn't change its contents when clicking on it */
if (!toggle.hasAttribute('data-toggle-alt-content')) {
return;
}
if (!toggle.hasAttribute('data-toggle-original-content')) {
toggle.setAttribute('data-toggle-original-content', toggle.innerHTML);
}
var currentContent = toggle.innerHTML;
var originalContent = toggle.getAttribute('data-toggle-original-content');
var altContent = toggle.getAttribute('data-toggle-alt-content');
toggle.innerHTML = currentContent !== altContent ? altContent : originalContent;
});
/* Prevents from disallowing clicks on links inside toggles */
var toggleLinks = toggles[i].querySelectorAll('a');
for (var j = 0; j < toggleLinks.length; j++) {
addEventListener(toggleLinks[j], 'click', function(e) {
e.stopPropagation();
});
}
toggles[i].setAttribute('data-processed', 'true');
}
},
createFilters: function() {
document.querySelectorAll('[data-filters] [data-filter]').forEach(function (filter) {
var filters = filter.closest('[data-filters]'),
type = 'choice',
name = filter.dataset.filter,
ucName = name.charAt(0).toUpperCase()+name.slice(1),
list = document.createElement('ul'),
values = filters.dataset['filter'+ucName] || filters.querySelectorAll('[data-filter-'+name+']'),
labels = {},
defaults = null,
indexed = {},
processed = {};
if (typeof values === 'string') {
type = 'level';
labels = values.split(',');
values = values.toLowerCase().split(',');
defaults = values.length - 1;
}
addClass(list, 'filter-list');
addClass(list, 'filter-list-'+type);
values.forEach(function (value, i) {
if (value instanceof HTMLElement) {
value = value.dataset['filter'+ucName];
}
if (value in processed) {
return;
}
var option = document.createElement('li'),
label = i in labels ? labels[i] : value,
active = false,
matches;
if ('' === label) {
option.innerHTML = '<em>(none)</em>';
} else {
option.innerText = label;
}
option.dataset.filter = value;
option.setAttribute('title', 1 === (matches = filters.querySelectorAll('[data-filter-'+name+'="'+value+'"]').length) ? 'Matches 1 row' : 'Matches '+matches+' rows');
indexed[value] = i;
list.appendChild(option);
addEventListener(option, 'click', function () {
if ('choice' === type) {
filters.querySelectorAll('[data-filter-'+name+']').forEach(function (row) {
if (option.dataset.filter === row.dataset['filter'+ucName]) {
toggleClass(row, 'filter-hidden-'+name);
}
});
toggleClass(option, 'active');
} else if ('level' === type) {
if (i === this.parentNode.querySelectorAll('.active').length - 1) {
return;
}
this.parentNode.querySelectorAll('li').forEach(function (currentOption, j) {
if (j <= i) {
addClass(currentOption, 'active');
if (i === j) {
addClass(currentOption, 'last-active');
} else {
removeClass(currentOption, 'last-active');
}
} else {
removeClass(currentOption, 'active');
removeClass(currentOption, 'last-active');
}
});
filters.querySelectorAll('[data-filter-'+name+']').forEach(function (row) {
if (i < indexed[row.dataset['filter'+ucName]]) {
addClass(row, 'filter-hidden-'+name);
} else {
removeClass(row, 'filter-hidden-'+name);
}
});
}
});
if ('choice' === type) {
active = null === defaults || 0 <= defaults.indexOf(value);
} else if ('level' === type) {
active = i <= defaults;
if (active && i === defaults) {
addClass(option, 'last-active');
}
}
if (active) {
addClass(option, 'active');
} else {
filters.querySelectorAll('[data-filter-'+name+'="'+value+'"]').forEach(function (row) {
toggleClass(row, 'filter-hidden-'+name);
});
}
processed[value] = true;
});
if (1 < list.childNodes.length) {
filter.appendChild(list);
filter.dataset.filtered = '';
}
});
}
};
})();
Sfjs.addEventListener(document, 'DOMContentLoaded', function() {
Sfjs.createTabs();
Sfjs.createToggles();
Sfjs.createFilters();
});
/*]]>*/

View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="<?= $this->charset ?>" />
<meta name="robots" content="noindex,nofollow,noarchive" />
<title>An Error Occurred: <?= $statusText ?></title>
<style><?= $this->include('assets/css/error.css') ?></style>
</head>
<body>
<div class="container">
<h1>Oops! An Error Occurred</h1>
<h2>The server returned a "<?= $statusCode ?> <?= $statusText ?>".</h2>
<p>
Something is broken. Please let us know what you were doing when this error occurred.
We will fix it as soon as possible. Sorry for any inconvenience caused.
</p>
</div>
</body>
</html>

View File

@ -0,0 +1,116 @@
<div class="exception-summary <?= !$exceptionMessage ? 'exception-without-message' : '' ?>">
<div class="exception-metadata">
<div class="container">
<h2 class="exception-hierarchy">
<?php foreach (array_reverse($exception->getAllPrevious(), true) as $index => $previousException) { ?>
<a href="#trace-box-<?= $index + 2 ?>"><?= $this->abbrClass($previousException->getClass()) ?></a>
<span class="icon"><?= $this->include('assets/images/chevron-right.svg') ?></span>
<?php } ?>
<a href="#trace-box-1"><?= $this->abbrClass($exception->getClass()) ?></a>
</h2>
<h2 class="exception-http">
HTTP <?= $statusCode ?> <small><?= $statusText ?></small>
</h2>
</div>
</div>
<div class="exception-message-wrapper">
<div class="container">
<h1 class="break-long-words exception-message<?= mb_strlen($exceptionMessage) > 180 ? ' long' : '' ?>"><?= $this->formatFileFromText(nl2br($exceptionMessage)) ?></h1>
<div class="exception-illustration hidden-xs-down">
<?= $this->include('assets/images/symfony-ghost.svg.php') ?>
</div>
</div>
</div>
</div>
<div class="container">
<div class="sf-tabs">
<div class="tab">
<?php
$exceptionAsArray = $exception->toArray();
$exceptionWithUserCode = [];
$exceptionAsArrayCount = \count($exceptionAsArray);
$last = \count($exceptionAsArray) - 1;
foreach ($exceptionAsArray as $i => $e) {
foreach ($e['trace'] as $trace) {
if ($trace['file'] && false === mb_strpos($trace['file'], '/vendor/') && false === mb_strpos($trace['file'], '/var/cache/') && $i < $last) {
$exceptionWithUserCode[] = $i;
}
}
}
?>
<h3 class="tab-title">
<?php if ($exceptionAsArrayCount > 1) { ?>
Exceptions <span class="badge"><?= $exceptionAsArrayCount ?></span>
<?php } else { ?>
Exception
<?php } ?>
</h3>
<div class="tab-content">
<?php
foreach ($exceptionAsArray as $i => $e) {
echo $this->include('views/traces.html.php', [
'exception' => $e,
'index' => $i + 1,
'expand' => \in_array($i, $exceptionWithUserCode, true) || ([] === $exceptionWithUserCode && 0 === $i),
]);
}
?>
</div>
</div>
<?php if ($logger) { ?>
<div class="tab <?= !$logger->getLogs() ? 'disabled' : '' ?>">
<h3 class="tab-title">
Logs
<?php if ($logger->countErrors()) { ?><span class="badge status-error"><?= $logger->countErrors() ?></span><?php } ?>
</h3>
<div class="tab-content">
<?php if ($logger->getLogs()) { ?>
<?= $this->include('views/logs.html.php', ['logs' => $logger->getLogs()]) ?>
<?php } else { ?>
<div class="empty">
<p>No log messages</p>
</div>
<?php } ?>
</div>
</div>
<?php } ?>
<div class="tab">
<h3 class="tab-title">
<?php if ($exceptionAsArrayCount > 1) { ?>
Stack Traces <span class="badge"><?= $exceptionAsArrayCount ?></span>
<?php } else { ?>
Stack Trace
<?php } ?>
</h3>
<div class="tab-content">
<?php
foreach ($exceptionAsArray as $i => $e) {
echo $this->include('views/traces_text.html.php', [
'exception' => $e,
'index' => $i + 1,
'numExceptions' => $exceptionAsArrayCount,
]);
}
?>
</div>
</div>
<?php if ($currentContent) { ?>
<div class="tab">
<h3 class="tab-title">Output content</h3>
<div class="tab-content">
<?= $currentContent ?>
</div>
</div>
<?php } ?>
</div>
</div>

View File

@ -0,0 +1,41 @@
<!-- <?= $_message = sprintf('%s (%d %s)', $exceptionMessage, $statusCode, $statusText) ?> -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="<?= $this->charset ?>" />
<meta name="robots" content="noindex,nofollow" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title><?= $_message ?></title>
<link rel="icon" type="image/png" href="<?= $this->include('assets/images/favicon.png.base64') ?>">
<style><?= $this->include('assets/css/exception.css') ?></style>
<style><?= $this->include('assets/css/exception_full.css') ?></style>
</head>
<body>
<header>
<div class="container">
<h1 class="logo"><?= $this->include('assets/images/symfony-logo.svg') ?> Symfony Exception</h1>
<div class="help-link">
<a href="https://symfony.com/doc/<?= Symfony\Component\HttpKernel\Kernel::VERSION ?>/index.html">
<span class="icon"><?= $this->include('assets/images/icon-book.svg') ?></span>
<span class="hidden-xs-down">Symfony</span> Docs
</a>
</div>
<div class="help-link">
<a href="https://symfony.com/support">
<span class="icon"><?= $this->include('assets/images/icon-support.svg') ?></span>
<span class="hidden-xs-down">Symfony</span> Support
</a>
</div>
</div>
</header>
<?= $this->include('views/exception.html.php', $context) ?>
<script>
<?= $this->include('assets/js/exception.js') ?>
</script>
</body>
</html>
<!-- <?= $_message ?> -->

View File

@ -0,0 +1,42 @@
<table class="logs" data-filter-level="Emergency,Alert,Critical,Error,Warning,Notice,Info,Debug" data-filters>
<?php $channelIsDefined = isset($logs[0]['channel']) ?>
<thead>
<tr>
<th data-filter="level">Level</th>
<?php if ($channelIsDefined) { ?><th data-filter="channel">Channel</th><?php } ?>
<th class="full-width">Message</th>
</tr>
</thead>
<tbody>
<?php
foreach ($logs as $log) {
if ($log['priority'] >= 400) {
$status = 'error';
} elseif ($log['priority'] >= 300) {
$status = 'warning';
} else {
$severity = $log['context']['exception']['severity'] ?? false;
$status = E_DEPRECATED === $severity || E_USER_DEPRECATED === $severity ? 'warning' : 'normal';
} ?>
<tr class="status-<?= $status ?>" data-filter-level="<?= strtolower($this->escape($log['priorityName'])) ?>"<?php if ($channelIsDefined) { ?> data-filter-channel="<?= $this->escape($log['channel']) ?>"<?php } ?>
<td class="text-small" nowrap>
<span class="colored text-bold"><?= $this->escape($log['priorityName']) ?></span>
<span class="text-muted newline"><?= (new \DateTime($log['timestamp']))->format('H:i:s') ?></span>
</td>
<?php if ($channelIsDefined) { ?>
<td class="text-small text-bold nowrap">
<?= $this->escape($log['channel']) ?>
</td>
<?php } ?>
<td>
<?= $this->formatLogMessage($log['message'], $log['context']) ?>
<?php if ($log['context']) { ?>
<pre class="text-muted prewrap m-t-5"><?= json_encode($log['context'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?></pre>
<?php } ?>
</td>
</tr>
<?php
} ?>
</tbody>
</table>

View File

@ -0,0 +1,40 @@
<div class="trace-line-header break-long-words <?= $trace['file'] ? 'sf-toggle' : '' ?>" data-toggle-selector="#trace-html-<?= $prefix ?>-<?= $i ?>" data-toggle-initial="<?= 'expanded' === $style ? 'display' : '' ?>">
<?php if ($trace['file']) { ?>
<span class="icon icon-close"><?= $this->include('assets/images/icon-minus-square.svg') ?></span>
<span class="icon icon-open"><?= $this->include('assets/images/icon-plus-square.svg') ?></span>
<?php } ?>
<?php if ('compact' !== $style && $trace['function']) { ?>
<span class="trace-class"><?= $this->abbrClass($trace['class']) ?></span><?php if ($trace['type']) { ?><span class="trace-type"><?= $trace['type'] ?></span><?php } ?><span class="trace-method"><?= $trace['function'] ?></span><span class="trace-arguments">(<?= $this->formatArgs($trace['args']) ?>)</span>
<?php } ?>
<?php if ($trace['file']) { ?>
<?php
$lineNumber = $trace['line'] ?: 1;
$fileLink = $this->getFileLink($trace['file'], $lineNumber);
$filePath = strtr(strip_tags($this->formatFile($trace['file'], $lineNumber)), [' at line '.$lineNumber => '']);
$filePathParts = explode(DIRECTORY_SEPARATOR, $filePath);
?>
<span class="block trace-file-path">
in
<a href="<?= $fileLink ?>">
<?= implode(\DIRECTORY_SEPARATOR, array_slice($filePathParts, 0, -1)).\DIRECTORY_SEPARATOR ?><strong><?= end($filePathParts) ?></strong>
</a>
<?php if ('compact' === $style && $trace['function']) { ?>
<span class="trace-type"><?= $trace['type'] ?></span>
<span class="trace-method"><?= $trace['function'] ?></span>
<?php } ?>
(line <?= $lineNumber ?>)
</span>
<?php } ?>
</div>
<?php if ($trace['file']) { ?>
<div id="trace-html-<?= $prefix.'-'.$i ?>" class="trace-code sf-toggle-content">
<?= strtr($this->fileExcerpt($trace['file'], $trace['line'], 5), [
'#DD0000' => 'var(--highlight-string)',
'#007700' => 'var(--highlight-keyword)',
'#0000BB' => 'var(--highlight-default)',
'#FF8000' => 'var(--highlight-comment)',
]) ?>
</div>
<?php } ?>

View File

@ -0,0 +1,42 @@
<div class="trace trace-as-html" id="trace-box-<?= $index ?>">
<div class="trace-details">
<div class="trace-head">
<span class="sf-toggle" data-toggle-selector="#trace-html-<?= $index ?>" data-toggle-initial="<?= $expand ? 'display' : '' ?>">
<h3 class="trace-class">
<span class="icon icon-close"><?= $this->include('assets/images/icon-minus-square-o.svg') ?></span>
<span class="icon icon-open"><?= $this->include('assets/images/icon-plus-square-o.svg') ?></span>
<span class="trace-namespace">
<?= implode('\\', array_slice(explode('\\', $exception['class']), 0, -1)) ?><?= \count(explode('\\', $exception['class'])) > 1 ? '\\' : '' ?>
</span>
<?= ($parts = explode('\\', $exception['class'])) ? end($parts) : '' ?>
</h3>
<?php if ($exception['message'] && $index > 1) { ?>
<p class="break-long-words trace-message"><?= $this->escape($exception['message']) ?></p>
<?php } ?>
</span>
</div>
<div id="trace-html-<?= $index ?>" class="sf-toggle-content">
<?php
$isFirstUserCode = true;
foreach ($exception['trace'] as $i => $trace) {
$isVendorTrace = $trace['file'] && (false !== mb_strpos($trace['file'], '/vendor/') || false !== mb_strpos($trace['file'], '/var/cache/'));
$displayCodeSnippet = $isFirstUserCode && !$isVendorTrace;
if ($displayCodeSnippet) {
$isFirstUserCode = false;
} ?>
<div class="trace-line <?= $isVendorTrace ? 'trace-from-vendor' : '' ?>">
<?= $this->include('views/trace.html.php', [
'prefix' => $index,
'i' => $i,
'trace' => $trace,
'style' => $isVendorTrace ? 'compact' : ($displayCodeSnippet ? 'expanded' : ''),
]) ?>
</div>
<?php
} ?>
</div>
</div>
</div>

View File

@ -0,0 +1,43 @@
<table class="trace trace-as-text">
<thead class="trace-head">
<tr>
<th class="sf-toggle" data-toggle-selector="#trace-text-<?= $index ?>" data-toggle-initial="<?= 1 === $index ? 'display' : '' ?>">
<h3 class="trace-class">
<?php if ($numExceptions > 1) { ?>
<span class="text-muted">[<?= $numExceptions - $index + 1 ?>/<?= $numExceptions ?>]</span>
<?php } ?>
<?= ($parts = explode('\\', $exception['class'])) ? end($parts) : '' ?>
<span class="icon icon-close"><?= $this->include('assets/images/icon-minus-square-o.svg') ?></span>
<span class="icon icon-open"><?= $this->include('assets/images/icon-plus-square-o.svg') ?></span>
</h3>
</th>
</tr>
</thead>
<tbody id="trace-text-<?= $index ?>">
<tr>
<td>
<?php if ($exception['trace']) { ?>
<pre class="stacktrace">
<?php
echo $exception['class'].":\n";
if ($exception['message']) {
echo $exception['message']."\n";
}
foreach ($exception['trace'] as $trace) {
echo "\n ";
if ($trace['function']) {
echo 'at '.$trace['class'].$trace['type'].$trace['function'].'('.$this->formatArgsAsText($trace['args']).')';
}
if ($trace['file'] && $trace['line']) {
echo ($trace['function'] ? "\n (" : 'at ').strtr(strip_tags($this->formatFile($trace['file'], $trace['line'])), [' at line '.$trace['line'] => '']).':'.$trace['line'].($trace['function'] ? ')' : '');
}
}
?>
</pre>
<?php } ?>
</td>
</tr>
</tbody>
</table>

View File

@ -20,7 +20,7 @@ class HtmlErrorRendererTest extends TestCase
public function testRender()
{
$exception = FlattenException::createFromThrowable(new \RuntimeException('Foo'));
$expected = '<!DOCTYPE html>%A<html>%A<head>%A<title>Internal Server Error</title>%A<h1 class="break-long-words exception-message">Foo</h1>%A<abbr title="RuntimeException">RuntimeException</abbr>%A';
$expected = '<!-- Foo (500 Internal Server Error) -->%A<!DOCTYPE html>%A<html lang="en">%A<title>Foo (500 Internal Server Error)</title>%A<!-- Foo (500 Internal Server Error) -->';
$this->assertStringMatchesFormat($expected, (new HtmlErrorRenderer(true))->render($exception));
}