From 641d7653edf794463d4bf3e465eb493b6476276b Mon Sep 17 00:00:00 2001 From: Andrey Kutejko Date: Sun, 11 Jan 2015 01:49:38 +0200 Subject: [PATCH] hooks & native templates for error pages --- ipf/error/404.html | 6 + ipf/error/404.php | 10 + ipf/error/500.html | 6 + ipf/error/500.php | 10 + ipf/error/500_debug.html | 113 +++++++ ipf/error/500_debug.php | 148 +++++++++ ipf/error/page.php | 35 +++ ipf/http/response/notfound.php | 12 +- ipf/http/response/servererror.php | 4 +- ipf/http/response/servererrordebug.php | 402 ------------------------- ipf/middleware/error.php | 5 +- 11 files changed, 332 insertions(+), 419 deletions(-) create mode 100644 ipf/error/404.html create mode 100644 ipf/error/404.php create mode 100644 ipf/error/500.html create mode 100644 ipf/error/500.php create mode 100644 ipf/error/500_debug.html create mode 100644 ipf/error/500_debug.php create mode 100644 ipf/error/page.php delete mode 100644 ipf/http/response/servererrordebug.php diff --git a/ipf/error/404.html b/ipf/error/404.html new file mode 100644 index 0000000..c62e3a5 --- /dev/null +++ b/ipf/error/404.html @@ -0,0 +1,6 @@ + +

Error 404

+

Page not found

+

Sorry, it appears the page you were looking for does not exist anymore or might have been moved.

+

Go to home page or go back to previous page.

+ diff --git a/ipf/error/404.php b/ipf/error/404.php new file mode 100644 index 0000000..0f88bca --- /dev/null +++ b/ipf/error/404.php @@ -0,0 +1,10 @@ + +

Error 500

+

Server error

+

Our apologies…

+

Go to home page or go back to previous page.

+ diff --git a/ipf/error/500.php b/ipf/error/500.php new file mode 100644 index 0000000..c2e3f95 --- /dev/null +++ b/ipf/error/500.php @@ -0,0 +1,10 @@ +exception) . ' making ' . $this->request->method . ' request to ' . $this->request->SERVER['REQUEST_URI']; + +$msg = $this->exception->getMessage(); +if ($this->exception->getCode()) + $msg = $this->exception->getCode(). ' : ' . $msg; + +$frames = $this->frames($this->exception); + +?> + + + + <?php $this->e($desc) ?> + + + + +
+

e($desc) ?>

+

e($msg) ?>

+

request->method ?> e($this->request->SERVER['REQUEST_URI']) ?>

+
+ + + + +
+
+

Request

+ + values('$_GET', $_GET) ?> + + values('$_POST', $_POST) ?> + + values('$_COOKIE', $_COOKIE) ?> + + values('$_SERVER', $_SERVER) ?> + + values('$_ENV', $_ENV) ?> + + values('Raw Headers', $this->request->getHeaders()) ?> + + request->getBody()): ?> +

Raw Body

+
e($this->request->getBody()) ?>
+ +
+ +
+

Response

+ values('Headers', $this->parse_headers(headers_list())) ?> +
+ + +
+

+

+ + sourceFile($frame['file'], $frame['line']) : '' ?> + + values('Arguments', $frame['args']) ?> +
+ +
+ + + diff --git a/ipf/error/500_debug.php b/ipf/error/500_debug.php new file mode 100644 index 0000000..85b5014 --- /dev/null +++ b/ipf/error/500_debug.php @@ -0,0 +1,148 @@ +__debugInfo() : get_object_vars($v); + $result[] = get_class($v) . ' Object'; + $result[] = '{'; + foreach ($props as $key => $val) { + $result[] = ' [' . $key . '] => ' . $this->debug_print_r($val, $indent+2); + } + $result[] = '}'; + } elseif ($v instanceof stdClass) { + $result[] = 'stdClass'; + $result[] = '{'; + foreach ($v as $k => $v) { + $result[] = ' ' . $k . ' => ' . $this->debug_print_r($v, $indent+2); + } + $result[] = '}'; + } elseif (is_array($v)) { + if ($v) { + $result[] = 'Array'; + $result[] = '{'; + foreach ($v as $k => $v) { + $result[] = ' [' . $k . '] => ' . $this->debug_print_r($v, $indent+2); + } + $result[] = '}'; + } else { + $result[] = 'Array()'; + } + } elseif (is_bool($v)) { + return $v ? 'true' : 'false'; + } else { + $result = explode("\n", print_r($v, true)); + } + + $i = str_repeat(' ', $indent); + foreach ($result as &$line) + $line = $i . $line; + return ltrim(implode("\n", $result)); + } + + function parse_headers($list) + { + $result = array(); + foreach ($list as $line) { + list($name, $value) = explode(':', $line, 2); + $result[$name] = $value; + } + return $result; + } + + function values($name, $values) + { + if ($values) { + ?>

+ + $value): ?> + + + + + +
e($name) ?>e($this->debug_print_r($value)) ?>
getFile(); + $line = $exception->getLine(); + foreach ($exception->getTrace() as $frame) { + $params = array(); + if ($frame["function"]) { + try { + if ($frame["class"]) { + $r = new ReflectionMethod($frame["class"]."::".$frame["function"]); + } else { + $r = new ReflectionFunction($frame["function"]); + } + foreach ($r->getParameters() as $p) { + $params[$p->getPosition()] = $p->getName(); + } + } catch (ReflectionException $e) { + } + } + + $args = array(); + foreach ($frame['args'] as $index => $arg) { + $name = array_key_exists($index, $params) ? $params[$index] : ""; + $args[$name] = $arg; + } + + $result[] = array( + 'file' => $file, + 'line' => $line, + 'class' => @$frame['class'], + 'type' => @$frame['type'], + 'function' => @$frame['function'], + 'func' => @$frame['class'].@$frame['type'].@$frame['function'], + 'args' => $args, + ); + + $file = $frame['file']; + $line = $frame['line']; + } + + $result[] = array( + 'file' => $file, + 'line' => $line, + 'class' => '', + 'type' => '', + 'function' => '', + 'func' => '', + 'args' => array(), + ); + + return $result; + } + + function sourceFile($file, $line) + { + $start = max($line - 10, 0); + $ol = "
    "; + + $rows = explode('
    ', nl2br(highlight_file($file, true))); + $rows = array_slice($rows, $start, 20); + + foreach ($rows as $k => $row) { + $attrs = ($k + $start === $line) ? ' class=current-line' : ''; + $ol .= "{$row}"; + } + $ol .= '
'; + return $ol; + } +} + diff --git a/ipf/error/page.php b/ipf/error/page.php new file mode 100644 index 0000000..3e9f0dd --- /dev/null +++ b/ipf/error/page.php @@ -0,0 +1,35 @@ +render($page->templateFile(), array('request' => $request, 'exception' => $exception)); + } + + abstract protected function templateFile(); +} + diff --git a/ipf/http/response/notfound.php b/ipf/http/response/notfound.php index 13cc0a1..2a551aa 100644 --- a/ipf/http/response/notfound.php +++ b/ipf/http/response/notfound.php @@ -4,17 +4,7 @@ class IPF_HTTP_Response_NotFound extends IPF_HTTP_Response { function __construct($request=null) { - try { - $context = array( - 'title' => '404 Not Found', - 'query_string' => @$_SERVER['QUERY_STRING'], - 'MEDIA_URL' => IPF::get('media_url'), - ); - $content = IPF_Shortcuts::RenderToString('404.html', $context, $request); - } catch (IPF_Exception $e) { - $content = '404 Not Found'; - } - parent::__construct($content); + parent::__construct(IPF_Error_Page::render404($request)); $this->status_code = 404; } } diff --git a/ipf/http/response/servererror.php b/ipf/http/response/servererror.php index c61f345..9b4e86d 100644 --- a/ipf/http/response/servererror.php +++ b/ipf/http/response/servererror.php @@ -2,9 +2,9 @@ class IPF_HTTP_Response_ServerError extends IPF_HTTP_Response { - function __construct($request, $e, $mimetype=null) + function __construct($request, $exception, $mimetype=null) { - parent::__construct('

500 Server Error

Our apologies…

Please return later

', $mimetype); + parent::__construct(IPF_Error_Page::render500($request, $exception), $mimetype); $this->status_code = 500; } } diff --git a/ipf/http/response/servererrordebug.php b/ipf/http/response/servererrordebug.php deleted file mode 100644 index 3fc7264..0000000 --- a/ipf/http/response/servererrordebug.php +++ /dev/null @@ -1,402 +0,0 @@ -status_code = 500; - $page = new IPF_HTTP_ServerErrorDebugPage($request, $exception); - $this->content = $page->html(); - } -} - -function debug_print_r($v, $indent=0) -{ - $result = array(); - - if (is_object($v) && method_exists($v, '__debugInfo')) { - $result[] = get_class($v) . ' Object'; - $result[] = '{'; - foreach ($v->__debugInfo() as $key => $val) { - $result[] = ' [' . $key . '] => ' . debug_print_r($val, $indent+2); - } - $result[] = '}'; - } elseif ($v instanceof stdClass) { - $result[] = 'stdClass'; - $result[] = '{'; - foreach ($v as $k => $v) { - $result[] = ' ' . $k . ' => ' . debug_print_r($v, $indent+2); - } - $result[] = '}'; - } elseif (is_array($v)) { - $result[] = 'Array'; - $result[] = '{'; - foreach ($v as $k => $v) { - $result[] = ' [' . $k . '] => ' . debug_print_r($v, $indent+2); - } - $result[] = '}'; - } elseif (is_bool($v)) { - return $v ? 'true' : 'false'; - } else { - $result = explode("\n", print_r($v, true)); - } - - $i = str_repeat(' ', $indent); - foreach ($result as &$line) - $line = $i . $line; - return ltrim(implode("\n", $result)); -} - -class IPF_HTTP_ServerErrorDebugPage -{ - private $request, $exception, $desc; - - public function __construct($request, $exception) - { - $this->request = $request; - $this->exception = $exception; - $this->desc = get_class($this->exception)." making ".$_SERVER['REQUEST_METHOD']." request to ".$_SERVER['REQUEST_URI']; - } - - function html() - { - return "\n" . Tag::create('html', array('lang' => 'en'), - Tag::head(null, - Tag::meta(array('charset' => 'utf-8')), - Tag::title(null, $this->desc), - $this->css(), - $this->js()), - Tag::body(null, - $this->Summary(), - $this->SectionStacktrace(), - $this->SectionRequest(), - $this->SectionResponse())); - } - - - function Summary() - { - $msg = $this->exception->getMessage(); - if ($this->exception->getCode()) - $msg = $this->exception->getCode(). ' : ' . $msg; - - return Tag::div(array('id' => 'summary'), - Tag::h1(null, $this->desc), - Tag::h2(null, $msg), - Tag::table(null, - Tag::tr(null, - Tag::th(null, 'PHP'), - Tag::td(null, $this->exception->getFile().', line '.$this->exception->getLine())), - Tag::tr(null, - Tag::th(null, 'URI'), - Tag::td(null, $_SERVER['REQUEST_METHOD'].' '.$_SERVER['REQUEST_URI'])))); - } - - - private function section($id, $title, $content) - { - return Tag::div(array('id' => $id, 'class' => 'section')) - ->append(Tag::h2() - ->append($title) - ->append(' ') - ->append(Tag::a(array('href' => '#', 'class' => 'section-toggle')) - ->append(Tag::span(array('class' => 'section-switch'))->append('▶')))) - ->append(Tag::div(array('class' => 'section-body')) - ->append($content)); - } - - - function SectionStacktrace() - { - return $this->section('traceback', 'Stacktrace', - $this->StacktraceFrames()); - } - - function StacktraceFrames() - { - $ul = Tag::ul(array('class' => 'traceback')); - - $frames = $this->exception->getTrace(); - foreach ($frames as $frame) { - if (!isset($frame['file'])) { - $frame['file'] = 'No File'; - $frame['line'] = '0'; - } - $name = ''; - if (isset($frame["class"])) - $name .= $frame["class"].$frame["type"]; - if (isset($frame["function"])) - $name .= $frame["function"]; - - $li = Tag::li(array('class' => 'frame'), - Tag::strong(null, $name), - ' ['.$frame['file'].', line '.$frame['line'].']'); - - if (isset($frame['args']) && count($frame['args']) > 0) { - $li->append($this->StacktraceArgs($frame)); - } - - if (is_readable($frame['file']) ) { - $li->append($this->StacktraceSourceFile($frame['file'], $frame['line'])); - } else { - $li->append(Tag::div(array('class' => 'commands'), 'No src available')); - } - - $ul->append($li); - } - return $ul; - } - - function StacktraceArgs($frame) - { - $params = array(); - if (isset($frame["function"])) { - if (isset($frame["class"])) { - $r = new ReflectionMethod($frame["class"]."::".$frame["function"]); - } else { - $r = new ReflectionFunction($frame["function"]); - } - $params = $r->getParameters(); - } - - $tbody = Tag::tbody(); - foreach ($frame['args'] as $k => $v) { - $name = isset($params[$k]) ? '$'.$params[$k]->name : '?'; - $tbody->append(Tag::tr(null, - Tag::td(null, $k), - Tag::td(null, $name), - Tag::td(array('class' => 'code'), - Tag::div()->raw(highlight_string(debug_print_r($v), true))))); - } - - return Tag::div(array('class' => 'section'), - Tag::div(array('class' => 'commands'), - Tag::a(array('href' => '#', 'class' => 'section-toggle'), - Tag::span(array('class' => 'section-switch'), '▶'), - ' Args')), - Tag::table(array('class' => 'section-body vars'), - Tag::thead(null, - Tag::tr(null, - Tag::th(null, 'Arg'), - Tag::th(null, 'Name'), - Tag::th(null, 'Value'))), - $tbody)); - } - - function StacktraceSourceFile($file, $line) - { - $start = max($line - 5, 0); - $ol = Tag::ol(array('start' => $start)); - - $rows = explode('
', nl2br(highlight_file($file, true))); - $rows = array_slice($rows, $start, 10); - - foreach ($rows as $k => $row) { - $li = Tag::li(null, Tag::code()->raw($row)); - if ($k + $start === $line) - $li->addClass('current-line'); - $ol->append($li); - } - - return Tag::div(array('class' => 'section'), - Tag::div(array('class' => 'commands'), - Tag::a(array('href' => '#', 'class' => 'section-toggle'), - Tag::span(array('class' => 'section-switch'), '▶'), - ' Src')), - Tag::div(array('class' => 'section-body context'), - $ol)); - } - - - function SectionRequest() - { - return $this->section('request', 'Request', array( - $this->RequestRaw(), - - Tag::h3(null, 'Request (parsed)'), - Tag::h4(null, '$_GET'), - $this->RequestSuperlobal($_GET), - - Tag::h4(null, '$_POST'), - $this->RequestSuperlobal($_POST), - - Tag::h4(null, '$_COOKIE'), - $this->RequestSuperlobal($_COOKIE), - - Tag::h4(null, '$_SERVER'), - $this->RequestSuperlobal($_SERVER), - - Tag::h4(null, '$_ENV'), - $this->RequestSuperlobal($_ENV), - )); - } - - - function RequestRaw() - { - return array( - Tag::h3(null, 'Request ', Tag::span('(raw)')), - $this->RequestRawHeaders(), - $this->RequestRawBody(), - ); - } - - function RequestRawHeaders() - { - $r = array( - Tag::h4('Headers'), - ); - $headers = $this->request->getHeaders(); - if ($headers) { - $h = Tag::p(array('class' => 'headers')); - foreach ($headers as $name => $val) { - $h->append($name.': '.$val)->append(Tag::br()); - } - $r[] = $h; - } else { - $r[] = Tag::p(null, 'No headers.'); - } - return $r; - } - - function RequestRawBody() - { - $body = $this->request->getBody(); - if (!$body) - return ''; - - return array( - Tag::h4('Body'), - Tag::p(array('class' => 'req', 'style' => 'padding-bottom: 2em'), - Tag::code(null, $body)), - ); - } - - - function RequestSuperlobal($var) - { - if (count($var)) { - $table = Tag::table(array('class' => 'req')); - - $thead = Tag::thead() - ->append(Tag::tr() - ->append(Tag::th(null, 'Variable')) - ->append(Tag::th(null, 'Value'))); - - $tbody = Tag::tbody(); - foreach ($var as $k => $v) { - $tbody->append(Tag::tr() - ->append(Tag::td() - ->append($k)) - ->append(Tag::td(array('class' => 'code')) - ->append(Tag::div()->append(print_r($v, true))))); - } - - return $table->append($thead)->append($tbody); - } else { - return Tag::p(array('class' => 'whitemsg'), 'No data'); - } - } - - - function SectionResponse() - { - return $this->section('response', 'Response', - $this->ResponseHeaders()); - } - - function ResponseHeaders() - { - $r = array( - Tag::h3('Headers'), - ); - $headers = headers_list(); - if ($headers) { - $h = Tag::p(array('class' => 'headers')); - foreach ($headers as $header) { - $h->append($header)->append(Tag::br()); - } - $r[] = $h; - } else { - $r[] = Tag::p(null, 'No headers.'); - } - return $r; - } - - - function css() - { - return Tag::style()->raw('html * { padding:0; margin:0; } -body * { padding:10px 20px; } -body * * { padding:0; } -body { font:small sans-serif; background: #70DBFF; } -body>div { border-bottom:1px solid #ddd; } -h1 { font-weight:normal; } -h2 { margin-bottom:.8em; } -h2 span { font-size:80%; color:#666; font-weight:normal; } -h2 a { text-decoration:none; } -h3 { margin:1em 0 .5em 0; } -h4 { margin:0.5em 0 .5em 0; font-weight: normal; font-style: italic; } -table { border:1px solid #ccc; border-collapse: collapse; background:white; } -tbody td, tbody th { vertical-align:top; padding:2px 3px; } -thead th { padding:1px 6px 1px 3px; background:#70FF94; text-align:left; font-weight:bold; font-size:11px; border:1px solid #ddd; } -tbody th { text-align:right; color:#666; padding-right:.5em; } -.vars table { margin:5px 0 2px 40px; } -.vars table td, table.req td { font-family:monospace; } -table td { background: #70FFDB; } -table td.code { width:95%;} -table td.code div { overflow:hidden; } -table.source th { color:#666; } -table.source td { font-family:monospace; white-space:pre; border-bottom:1px solid #eee; } -ul.traceback { list-style-type:none; } -ul.traceback li.frame { margin-bottom:1em; } -div.context { margin:5px 0 2px 40px; background: #fff; border: 1px dashed #666; } -div.context ol { padding-left:10px; margin:0 10px; list-style-position: inside; } -div.context ol li { font-family:monospace; white-space:pre; color:#666; cursor:pointer; } -div.context li.current-line { color:black; background-color:#70FF94; } -div.commands { margin-left: 40px; } -div.commands a { color:black; text-decoration:none; } -p.headers { background: #70FFDB; font-family:monospace; padding: 2px; } -#summary { background: #00B8F5; } -#summary h2 { font-weight: normal; color: #666; } -#traceback { background:#eee; } -#request { background:#f6f6f6; } -#response { background:#eee; } -#summary table { border:none; background:#00B8F5; } -#summary td { background:#00B8F5; } -.switch { text-decoration: none; } -.whitemsg { background:white; color:black;}'); - } - - function js() - { - return Tag::script()->raw(' -window.onload = function() { - function onClick(e, c) { - var args = Array.prototype.slice.call(arguments, 2); - if (e.addEventListener) - e.addEventListener("click", function(ev) { c.apply(this, args); ev.preventDefault(); }, false); - else - e.attachEvent("onclick", function(ev) { c.apply(this, args); ev.returnValue = false; }); - } - - var i, bd, sections = document.querySelectorAll(".section"); - for (i = 0; i < sections.length; ++i) { - bd = sections[i].querySelector(".section-body"); - bd.style.display = \'none\'; - onClick(sections[i].querySelector(".section-toggle"), function (sw, bd) { - bd.style.display = bd.style.display == \'none\' ? \'block\' : \'none\'; - - var uarr = String.fromCharCode(0x25b6); - var darr = String.fromCharCode(0x25bc); - sw.innerHTML = sw.innerHTML == uarr ? darr : uarr; - }, - sections[i].querySelector(".section-switch"), - bd); - } -}'); - } -} - diff --git a/ipf/middleware/error.php b/ipf/middleware/error.php index 69ba101..ee009f8 100644 --- a/ipf/middleware/error.php +++ b/ipf/middleware/error.php @@ -17,10 +17,7 @@ class IPF_Error_Middleware extends IPF_Middleware return $response; } catch (\Exception $exception) { error_log($exception); - if ($this->settings->get('debug')) - return new IPF_HTTP_Response_ServerErrorDebug($request, $exception); - else - return new IPF_HTTP_Response_ServerError($request, $exception); + return new IPF_HTTP_Response_ServerError($request, $exception); } } } -- 2.49.0