drupal-civicrm/vendor/symfony/debug/ExceptionHandler.php

411 lines
32 KiB
PHP
Raw Permalink Normal View History

2018-01-14 15:10:16 +02:00
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug;
use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\Debug\Exception\OutOfMemoryException;
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
/**
* ExceptionHandler converts an exception to a Response object.
*
* It is mostly useful in debug mode to replace the default PHP/XDebug
* output with something prettier and more useful.
*
* As this class is mainly used during Kernel boot, where nothing is yet
* available, the Response content is always HTML.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class ExceptionHandler
{
private $debug;
private $charset;
private $handler;
private $caughtBuffer;
private $caughtLength;
private $fileLinkFormat;
public function __construct($debug = true, $charset = null, $fileLinkFormat = null)
{
$this->debug = $debug;
$this->charset = $charset ?: ini_get('default_charset') ?: 'UTF-8';
$this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
}
/**
* Registers the exception handler.
*
* @param bool $debug Enable/disable debug mode, where the stack trace is displayed
* @param string|null $charset The charset used by exception messages
* @param string|null $fileLinkFormat The IDE link template
*
* @return static
*/
public static function register($debug = true, $charset = null, $fileLinkFormat = null)
{
$handler = new static($debug, $charset, $fileLinkFormat);
$prev = set_exception_handler(array($handler, 'handle'));
if (is_array($prev) && $prev[0] instanceof ErrorHandler) {
restore_exception_handler();
$prev[0]->setExceptionHandler(array($handler, 'handle'));
}
return $handler;
}
/**
* Sets a user exception handler.
*
* @param callable $handler An handler that will be called on Exception
*
* @return callable|null The previous exception handler if any
*/
public function setHandler(callable $handler = null)
{
$old = $this->handler;
$this->handler = $handler;
return $old;
}
/**
* Sets the format for links to source files.
*
* @param string|FileLinkFormatter $fileLinkFormat The format for links to source files
*
* @return string The previous file link format
*/
public function setFileLinkFormat($fileLinkFormat)
{
$old = $this->fileLinkFormat;
$this->fileLinkFormat = $fileLinkFormat;
return $old;
}
/**
* Sends a response for the given Exception.
*
* To be as fail-safe as possible, the exception is first handled
* by our simple exception handler, then by the user exception handler.
* The latter takes precedence and any output from the former is cancelled,
* if and only if nothing bad happens in this handling path.
*/
public function handle(\Exception $exception)
{
if (null === $this->handler || $exception instanceof OutOfMemoryException) {
$this->sendPhpResponse($exception);
return;
}
$caughtLength = $this->caughtLength = 0;
ob_start(function ($buffer) {
$this->caughtBuffer = $buffer;
return '';
});
$this->sendPhpResponse($exception);
while (null === $this->caughtBuffer && ob_end_flush()) {
// Empty loop, everything is in the condition
}
if (isset($this->caughtBuffer[0])) {
ob_start(function ($buffer) {
if ($this->caughtLength) {
// use substr_replace() instead of substr() for mbstring overloading resistance
$cleanBuffer = substr_replace($buffer, '', 0, $this->caughtLength);
if (isset($cleanBuffer[0])) {
$buffer = $cleanBuffer;
}
}
return $buffer;
});
echo $this->caughtBuffer;
$caughtLength = ob_get_length();
}
$this->caughtBuffer = null;
try {
call_user_func($this->handler, $exception);
$this->caughtLength = $caughtLength;
} catch (\Exception $e) {
if (!$caughtLength) {
// All handlers failed. Let PHP handle that now.
throw $exception;
}
}
}
/**
* Sends the error associated with the given Exception as a plain PHP response.
*
* This method uses plain PHP functions like header() and echo to output
* the response.
*
* @param \Exception|FlattenException $exception An \Exception or FlattenException instance
*/
public function sendPhpResponse($exception)
{
if (!$exception instanceof FlattenException) {
$exception = FlattenException::create($exception);
}
if (!headers_sent()) {
header(sprintf('HTTP/1.0 %s', $exception->getStatusCode()));
foreach ($exception->getHeaders() as $name => $value) {
header($name.': '.$value, false);
}
header('Content-Type: text/html; charset='.$this->charset);
}
echo $this->decorate($this->getContent($exception), $this->getStylesheet($exception));
}
/**
* Gets the full HTML content associated with the given exception.
*
* @param \Exception|FlattenException $exception An \Exception or FlattenException instance
*
* @return string The HTML content as a string
*/
public function getHtml($exception)
{
if (!$exception instanceof FlattenException) {
$exception = FlattenException::create($exception);
}
return $this->decorate($this->getContent($exception), $this->getStylesheet($exception));
}
/**
* Gets the HTML content associated with the given exception.
*
* @return string The content as a string
*/
public function getContent(FlattenException $exception)
{
switch ($exception->getStatusCode()) {
case 404:
$title = 'Sorry, the page you are looking for could not be found.';
break;
default:
$title = 'Whoops, looks like something went wrong.';
}
$content = '';
if ($this->debug) {
try {
$count = count($exception->getAllPrevious());
$total = $count + 1;
foreach ($exception->toArray() as $position => $e) {
$ind = $count - $position + 1;
$class = $this->formatClass($e['class']);
$message = nl2br($this->escapeHtml($e['message']));
$content .= sprintf(<<<'EOF'
<div class="trace trace-as-html">
<table class="trace-details">
<thead class="trace-head"><tr><th>
<h3 class="trace-class">
<span class="text-muted">(%d/%d)</span>
<span class="exception_title">%s</span>
</h3>
<p class="break-long-words trace-message">%s</p>
</th></tr></thead>
<tbody>
EOF
, $ind, $total, $class, $message);
foreach ($e['trace'] as $trace) {
$content .= '<tr><td>';
if ($trace['function']) {
$content .= sprintf('at <span class="trace-class">%s</span><span class="trace-type">%s</span><span class="trace-method">%s</span>(<span class="trace-arguments">%s</span>)', $this->formatClass($trace['class']), $trace['type'], $trace['function'], $this->formatArgs($trace['args']));
}
if (isset($trace['file']) && isset($trace['line'])) {
$content .= $this->formatPath($trace['file'], $trace['line']);
}
$content .= "</td></tr>\n";
}
$content .= "</tbody>\n</table>\n</div>\n";
}
} catch (\Exception $e) {
// something nasty happened and we cannot throw an exception anymore
if ($this->debug) {
$title = sprintf('Exception thrown when handling an exception (%s: %s)', get_class($e), $this->escapeHtml($e->getMessage()));
} else {
$title = 'Whoops, looks like something went wrong.';
}
}
}
$symfonyGhostImageContents = $this->getSymfonyGhostAsSvg();
return <<<EOF
<div class="exception-summary">
<div class="container">
<div class="exception-message-wrapper">
<h1 class="break-long-words exception-message">$title</h1>
<div class="exception-illustration hidden-xs-down">$symfonyGhostImageContents</div>
</div>
</div>
</div>
<div class="container">
$content
</div>
EOF;
}
/**
* Gets the stylesheet associated with the given exception.
*
* @return string The stylesheet as a string
*/
public function getStylesheet(FlattenException $exception)
{
return <<<'EOF'
body { background-color: #F9F9F9; color: #222; font: 14px/1.4 Helvetica, Arial, sans-serif; margin: 0; 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: 1px solid #E0E0E0; box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; width: 100%; }
table th, table td { border: solid #E0E0E0; border-width: 1px 0; padding: 8px 10px; }
table th { background-color: #E0E0E0; font-weight: bold; text-align: left; }
.hidden-xs-down { display: none; }
.block { display: block; }
.break-long-words { -ms-word-break: break-all; word-break: break-all; word-break: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; }
.text-muted { color: #999; }
.container { max-width: 1024px; margin: 0 auto; padding: 0 15px; }
.container::after { content: ""; display: table; clear: both; }
.exception-summary { background: #B0413E; 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: 30px; }
.exception-message-wrapper { display: flex; align-items: center; min-height: 70px; }
.exception-message { flex-grow: 1; padding: 30px 0; }
.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 .trace-class { color: #222; font-size: 18px; font-weight: bold; line-height: 1.3; margin: 0; position: relative; }
.trace-message { font-size: 14px; font-weight: normal; margin: .5em 0 0; }
.trace-file-path, .trace-file-path a { color: #222; margin-top: 3px; font-size: 13px; }
.trace-class { color: #B0413E; }
.trace-type { padding: 0 2px; }
.trace-method { color: #B0413E; font-weight: bold; }
.trace-arguments { color: #777; font-weight: normal; padding-left: 2px; }
@media (min-width: 575px) {
.hidden-xs-down { display: initial; }
}
EOF;
}
private function decorate($content, $css)
{
return <<<EOF
<!DOCTYPE html>
<html>
<head>
<meta charset="{$this->charset}" />
<meta name="robots" content="noindex,nofollow" />
<style>$css</style>
</head>
<body>
$content
</body>
</html>
EOF;
}
private function formatClass($class)
{
$parts = explode('\\', $class);
return sprintf('<abbr title="%s">%s</abbr>', $class, array_pop($parts));
}
private function formatPath($path, $line)
{
$file = $this->escapeHtml(preg_match('#[^/\\\\]*+$#', $path, $file) ? $file[0] : $path);
$fmt = $this->fileLinkFormat;
if ($fmt && $link = is_string($fmt) ? strtr($fmt, array('%f' => $path, '%l' => $line)) : $fmt->format($path, $line)) {
return sprintf('<span class="block trace-file-path">in <a href="%s" title="Go to source">%s (line %d)</a></span>', $this->escapeHtml($link), $file, $line);
}
return sprintf('<span class="block trace-file-path">in <a title="%s line %3$d"><strong>%s</strong> (line %d)</a></span>', $this->escapeHtml($path), $file, $line);
}
/**
* Formats an array as a string.
*
* @param array $args The argument array
*
* @return string
*/
private function formatArgs(array $args)
{
$result = array();
foreach ($args as $key => $item) {
if ('object' === $item[0]) {
$formattedValue = sprintf('<em>object</em>(%s)', $this->formatClass($item[1]));
} elseif ('array' === $item[0]) {
$formattedValue = sprintf('<em>array</em>(%s)', is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]);
} elseif ('null' === $item[0]) {
$formattedValue = '<em>null</em>';
} elseif ('boolean' === $item[0]) {
$formattedValue = '<em>'.strtolower(var_export($item[1], true)).'</em>';
} elseif ('resource' === $item[0]) {
$formattedValue = '<em>resource</em>';
} else {
$formattedValue = str_replace("\n", '', $this->escapeHtml(var_export($item[1], true)));
}
$result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", $this->escapeHtml($key), $formattedValue);
}
return implode(', ', $result);
}
/**
* HTML-encodes a string.
*/
private function escapeHtml($str)
{
return htmlspecialchars($str, ENT_COMPAT | ENT_SUBSTITUTE, $this->charset);
}
private function getSymfonyGhostAsSvg()
{
return '<svg viewBox="0 0 136 81" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path d="M92.358 20.403a23.168 23.168 0 0 1 9.003 1.881 23.67 23.67 0 0 1 5.197 3.079 24.257 24.257 0 0 1 3.457 3.296 24.771 24.771 0 0 1 5.042 9.396c.486 1.72.78 3.492.895 5.28l.008.142.028.158.015.246v13.875c.116.034.232.065.348.098.193.054.383.116.577.168.487.125.989.191 1.49.215.338.016.689.023 1.021-.059.021-.005.032-.029.048-.044l.095-.1c.243-.265.461-.552.663-.851.277-.408.523-.837.746-1.279l.042-.087c-.066-.012-.131-.026-.197-.04l-.099-.023a5.536 5.536 0 0 1-.694-.242 5.649 5.649 0 0 1-2.374-1.845 5.694 5.694 0 0 1-.824-1.594 6.514 6.514 0 0 1-.267-2.781c.045-.394.126-.779.233-1.159.079-.278.162-.562.307-.812.094-.163.129-.196.247-.341l.79-.882c.143-.143.174-.186.34-.303.249-.174.536-.289.834-.333.074-.011.15-.014.224-.02l1.188-.037c.173.004.217-.002.388.028s.211.05.375.105l.018.007c.059.026.119.05.176.079.151.076.179.104.313.2l.006-.021c.073-.187.084-.238.187-.41.077-.129.167-.249.27-.357.051-.054.108-.103.162-.154l1.124-.95c.14-.107.172-.14.327-.224.155-.085.199-.094.363-.154l.019-.006c.169-.043.211-.06.385-.077.174-.016.218-.007.392.003l1.446.158c.193.033.244.033.43.098.278.097.534.259.744.47.053.053.1.112.149.167l.923 1.158.149.213.028.054.017-.014.184-.125c.196-.104.196-.104.402-.184l1.386-.451c.064-.018.126-.038.19-.052.129-.028.259-.042.39-.043.16-.002.321.017.478.047.364.069.711.21 1.032.396.162.094.316.199.469.308.088.063.176.132.27.188l.021.011c.19.123.245.146.409.305.185.178.336.393.443.63.035.079.061.162.091.243l.439 1.428c.045.175.062.219.081.4.02.193.006.381-.015.573a7.79 7.79 0 0 1-.101.645c-.09.455-.212.901-.365 1.339-.128.366-.273.73-.445 1.077-.658 1.335-1.652 2.512-2.917 3.265a6.399 6.399 0 0 1-1.019.489 6.097 6.097 0 0 1-.631.203c-.226.058-.455.1-.686.134l-.096.012-.061.007c-.01.176-.022.352-.036.528-.034.39-.082.778-.153 1.163a14.258 14.258 0 0 1-.574 2.114c-.229.654-.484 1.306-.806 1.918a9.16 9.16 0 0 1-.386.656c-.219.348-.451.686-.697 1.013-.448.594-.946 1.148-1.521 1.614-.255.207-.52.397-.808.553-.9.489-1.919.648-2.921.735-.493.038-.986.059-1.478.099-.162.015-.324.033-.486.049-.145.011-.289.022-.434.03a15.768 15.768 0 0 1-2.778-.118c0 1.416.007 2.832-.001 4.248a9.737 9.737 0 0 1-.684 3.479 9.615 9.615 0 0 1-1.72 2.804 9.326 9.326 0 0 1-3.04 2.279 9.046 9.046 0 0 1-5.33.715 9.064 9.064 0 0 1-2.988-1.079 9.363 9.363 0 0 1-2.761-2.429 10.078 10.078 0 0 1-1.05 1.16 9.281 9.281 0 0 1-1.871 1.358 9.033 9.033 0 0 1-2.495.926 9.04 9.04 0 0 1-6.462-1.072 9.395 9.395 0 0 1-2.602-2.292l-.062-.08a10.896 10.896 0 0 1-.53.635 9.266 9.266 0 0 1-2.671 2.032 9.028 9.028 0 0 1-6.044.751 9.048 9.048 0 0 1-2.436-.934 9.343 9.343 0 0 1-2.286-1.803 9.572 9.572 0 0 1-1.783-2.757 9.705 9.705 0 0 1-.773-3.693V67.244c-.157.024-.314.047-.472.067-.487.06-.977.103-1.469.109-.313.004-.627-.009-.94-.028-.426-.025-.85-.065-1.273-.125-1.833-.264-3.65-.92-5.109-2.117a8.172 8.172 0 0 1-1.064-1.049 10.155 10.155 0 0 1-.878-1.236 15.277 15.277 0 0 1-.7-1.274 20.835 20.835 0 0 1-1.889-6.194l-.018-.142-.008-.061a6.47 6.47 0 0 1-.99-.297 6.135 6.135 0 0 1-.61-.285 6.587 6.587 0 0 1-.889-.562c-1.228-.924-2.124-2.259-2.668-3.711a9.947 9.947 0 0 1-.307-.99 10.288 10.288 0 0 1-.318-1.923c-.009-.147-.011-.293-.015-.44v-.037c.008-.175.004-.22.037-.393.033-.173.053-.213.11-.378l.561-1.417c.031-.068.06-.139.095-.206a2.028 2.028 0 0 1 .771-.803c.093-.054.194-.095.289-.145l.311-.179c.352-.194.714-.358 1.107-.44.213-.044.426-.061.643-.061l.034.001c.177.014.223.01.396.052.174.041.214.065.379.132l1.347.635c.073.04.15.076.221.121.142.091.272.2.388.325.154.166.176.222.297.414l.022.047.722-.762.168-.158c.165-.122.202-.161.385-.253.206-.102.429-.168.656-.193.076-.008.152-.008.228-.011l1.46.013c.177.011.223.007.397.046.175.038.215.061.381.126l.018.008c.154.08.196.094.338.196.142.102.169.137.294.259l.853.912.152-.067.191-.063.019-.005.196-.042c.177-.019.222-.031.401-.022.066.003.133.013.199.02l1.185.182c.073.016.147.027.219.047.288.08.558.227.784.428.151.135.177.181.303.339l.714 1.0
}
}