mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-21 09:38:48 +02:00
Portal: Improved debug informations on error page
SVN:trunk[4836]
This commit is contained in:
@@ -1,225 +0,0 @@
|
||||
<?php
|
||||
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
namespace Combodo\iTop\Portal\Handler;
|
||||
|
||||
use \Exception;
|
||||
use \Silex\Application;
|
||||
use \Symfony\Component\Debug\ExceptionHandler as BaseExceptionHandler;
|
||||
use \Symfony\Component\Debug\Exception\FlattenException;
|
||||
use \Symfony\Component\HttpFoundation\Request;
|
||||
use \IssueLog;
|
||||
use \Dict;
|
||||
|
||||
/**
|
||||
* Extends the default ExceptionHandler to provide a better template.
|
||||
*
|
||||
* @author Guillaume Lajarige
|
||||
*/
|
||||
class ExceptionHandler extends BaseExceptionHandler
|
||||
{
|
||||
private $debug;
|
||||
private $charset;
|
||||
private $handler;
|
||||
private $caughtBuffer;
|
||||
private $caughtLength;
|
||||
private $fileLinkFormat;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
IssueLog::Error('Portal: '.$exception->getMessage());
|
||||
|
||||
parent::handle($exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the HTML content associated with the given exception.
|
||||
*
|
||||
* @param FlattenException $exception A FlattenException instance
|
||||
*
|
||||
* @return string The content as a string
|
||||
*/
|
||||
public function getContent(FlattenException $exception)
|
||||
{
|
||||
switch ($exception->getStatusCode()) {
|
||||
case 404:
|
||||
$title = Dict::S('Error:HTTP:404');
|
||||
break;
|
||||
default:
|
||||
$title = Dict::S('Error:HTTP:500');
|
||||
}
|
||||
|
||||
$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
|
||||
<h2 class="block_exception clear_fix">
|
||||
<span class="exception_counter">%d/%d</span>
|
||||
<span class="exception_title">%s%s:</span>
|
||||
<span class="exception_message">%s</span>
|
||||
</h2>
|
||||
<div class="block">
|
||||
<ol class="traces list_exception">
|
||||
|
||||
EOF
|
||||
, $ind, $total, $class, $this->formatPath($e['trace'][0]['file'], $e['trace'][0]['line']), $message);
|
||||
foreach ($e['trace'] as $trace) {
|
||||
$content .= ' <li>';
|
||||
if ($trace['function']) {
|
||||
$content .= sprintf('at %s%s%s(%s)', $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 .= "</li>\n";
|
||||
}
|
||||
|
||||
$content .= " </ol>\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.';
|
||||
}
|
||||
}
|
||||
}
|
||||
else{
|
||||
$content = $exception->getMessage();
|
||||
}
|
||||
|
||||
return <<<EOF
|
||||
<div id="sf-resetcontent" class="sf-reset">
|
||||
<h1>$title</h1>
|
||||
<div class="content">$content</div>
|
||||
</div>
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the stylesheet associated with the given exception.
|
||||
*
|
||||
* @param FlattenException $exception A FlattenException instance
|
||||
*
|
||||
* @return string The stylesheet as a string
|
||||
*/
|
||||
public function getStylesheet(FlattenException $exception)
|
||||
{
|
||||
$parentStylesheet = parent::getStylesheet($exception);
|
||||
$stylesheet = <<<EOF
|
||||
.sf-reset .content{ background-color: #FFFFFF; padding: 15px 28px; margin-bottom: 20px; -webkit-border-radius: 10px; -moz-border-radius: 10px; border-radius: 10px; border: 1px solid #ccc; font-size: 13px; }
|
||||
EOF;
|
||||
|
||||
return $stylesheet . $parentStylesheet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Duplicated as the original one is private.
|
||||
*
|
||||
* @param $class
|
||||
* @return string
|
||||
*/
|
||||
protected function formatClass($class)
|
||||
{
|
||||
$parts = explode('\\', $class);
|
||||
|
||||
return sprintf('<abbr title="%s">%s</abbr>', $class, array_pop($parts));
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Duplicated as the original one is private.
|
||||
*
|
||||
* 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 ('string' === $item[0]) {
|
||||
$formattedValue = sprintf("'%s'", $this->escapeHtml($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", '', var_export($this->escapeHtml((string) $item[1]), true));
|
||||
}
|
||||
|
||||
$result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue);
|
||||
}
|
||||
|
||||
return implode(', ', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Duplicated as the original one is private.
|
||||
*
|
||||
* HTML-encodes a string.
|
||||
*/
|
||||
private function escapeHtml($str)
|
||||
{
|
||||
return htmlspecialchars($str, ENT_QUOTES | (PHP_VERSION_ID >= 50400 ? ENT_SUBSTITUTE : 0), $this->charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Duplicated as the original one is private.
|
||||
*
|
||||
* @param $path
|
||||
* @param $line
|
||||
* @return string
|
||||
*/
|
||||
private function formatPath($path, $line)
|
||||
{
|
||||
$path = $this->escapeHtml($path);
|
||||
$file = preg_match('#[^/\\\\]*$#', $path, $file) ? $file[0] : $path;
|
||||
|
||||
if ($linkFormat = $this->fileLinkFormat) {
|
||||
$link = strtr($this->escapeHtml($linkFormat), array('%f' => $path, '%l' => (int) $line));
|
||||
|
||||
return sprintf(' in <a href="%s" title="Go to source">%s line %d</a>', $link, $file, $line);
|
||||
}
|
||||
|
||||
return sprintf(' in <a title="%s line %3$d" ondblclick="var f=this.innerHTML;this.innerHTML=this.title;this.title=f;">%s line %d</a>', $path, $file, $line);
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,7 @@ namespace Combodo\iTop\Portal\Helper;
|
||||
use \Exception;
|
||||
use \Silex\Application;
|
||||
use \Symfony\Component\Debug\ErrorHandler;
|
||||
//use \Symfony\Component\Debug\ExceptionHandler;
|
||||
use \Combodo\iTop\Portal\Handler\ExceptionHandler;
|
||||
use \Symfony\Component\Debug\ExceptionHandler;
|
||||
use \Symfony\Component\HttpFoundation\Request;
|
||||
use \Twig_Environment;
|
||||
use \Twig_SimpleFilter;
|
||||
@@ -259,7 +258,7 @@ class ApplicationHelper
|
||||
ExceptionHandler::register(($oApp['debug'] === true));
|
||||
|
||||
// Intercepting manually aborted request
|
||||
if (!$oApp['debug'])
|
||||
if (1 || !$oApp['debug'])
|
||||
{
|
||||
$oApp->error(function(Exception $e, $code) use ($oApp)
|
||||
{
|
||||
@@ -288,6 +287,66 @@ class ApplicationHelper
|
||||
}
|
||||
else
|
||||
{
|
||||
// Preparing debug trace
|
||||
$aSteps = array();
|
||||
foreach($e->getTrace() as $aStep)
|
||||
{
|
||||
// - Default file name
|
||||
if(!isset($aStep['file']))
|
||||
{
|
||||
$aStep['file'] = '';
|
||||
}
|
||||
$aFileParts = explode('\\', $aStep['file']);
|
||||
// - Default line number
|
||||
if(!isset($aStep['line']))
|
||||
{
|
||||
$aStep['line'] = 'unknown';
|
||||
}
|
||||
// - Default class name
|
||||
if(isset($aStep['class']) && isset($aStep['function']) && isset($aStep['type']))
|
||||
{
|
||||
$aClassParts = explode('\\', $aStep['class']);
|
||||
$sClassName = $aClassParts[count($aClassParts)-1];
|
||||
$sClassFQ = $aStep['class'];
|
||||
|
||||
$aArgsAsString = array();
|
||||
foreach($aStep['args'] as $arg)
|
||||
{
|
||||
if(is_array($arg))
|
||||
{
|
||||
$aArgsAsString[] = 'array(...)';
|
||||
}
|
||||
elseif(is_object($arg))
|
||||
{
|
||||
$aArgsAsString[] = 'object('.get_class($arg).')';
|
||||
}
|
||||
else
|
||||
{
|
||||
$aArgsAsString[] = $arg;
|
||||
}
|
||||
}
|
||||
|
||||
$sFunctionCall = $sClassName . $aStep['type'] . $aStep['function'] . '(' . implode(', ', $aArgsAsString) . ')';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sClassName = null;
|
||||
$sClassFQ = null;
|
||||
$sFunctionCall = null;
|
||||
}
|
||||
|
||||
$aSteps[] = array(
|
||||
'file_fq' => $aStep['file'],
|
||||
'file_name' => $aFileParts[count($aFileParts)-1],
|
||||
'line' => $aStep['line'],
|
||||
'class_name' => $sClassName,
|
||||
'class_fq' => $sClassFQ,
|
||||
'function_call' => $sFunctionCall,
|
||||
);
|
||||
}
|
||||
|
||||
$aData['debug_trace_steps'] = $aSteps;
|
||||
|
||||
$oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/errors/layout.html.twig', $aData);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
margin: 50px auto;
|
||||
text-align: center;
|
||||
padding: 25px;
|
||||
max-width: 600px;
|
||||
min-width: 600px;
|
||||
max-width: 1000px;
|
||||
}
|
||||
h1, h2, h3, p {
|
||||
margin: 0;
|
||||
@@ -27,6 +28,12 @@
|
||||
vertical-align: -5%;
|
||||
margin-right: 5px;
|
||||
}
|
||||
abbr[title]{
|
||||
border-bottom: none;
|
||||
}
|
||||
.traces.list_exception{
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="container">
|
||||
@@ -40,5 +47,18 @@
|
||||
<a class="btn btn-default" href="{{ app.url_generator.generate('p_home') }}"><span class="fa fa-home"></span> {{ 'Page:GoPortalHome'|dict_s }}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% if app['debug'] == true %}
|
||||
<div class="well">
|
||||
<ol class="traces list_exception">
|
||||
{% for aStep in debug_trace_steps %}
|
||||
<li>
|
||||
{% if aStep.function_call is not null %}at <abbr title="{{ aStep.class_fq }}">{{ aStep.function_call }}</abbr>{% endif %}
|
||||
in <a title="{{ aStep.file_fq }}">{{ aStep.file_name }}</a> line {{ aStep.line }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -31,7 +31,6 @@ require_once APPROOT . '/core/moduledesign.class.inc.php';
|
||||
require_once APPROOT . '/application/loginwebpage.class.inc.php';
|
||||
require_once APPROOT . '/sources/autoload.php';
|
||||
// Portal
|
||||
require_once __DIR__ . '/../src/handlers/exceptionhandler.class.inc.php';
|
||||
require_once __DIR__ . '/../src/providers/urlgeneratorserviceprovider.class.inc.php';
|
||||
require_once __DIR__ . '/../src/helpers/urlgeneratorhelper.class.inc.php';
|
||||
require_once __DIR__ . '/../src/providers/contextmanipulatorserviceprovider.class.inc.php';
|
||||
|
||||
Reference in New Issue
Block a user