<?php
/**
* Dubrox_PhpDebugger allows to show debugs informations in a better looking way
* without being intrusive for the HTML result, allowing some other useful operations.
*
* @version 1.0
* @author Luca Lauretta (aka. dubrox)
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
* @todo improve documentation, optimize memory usage, add error sorting feature, add unique ids to errors for error references, ...
*/
class Dubrox_PhpDebugger {
const version = '1.0';
const site = 'http://sourceforge.net/projects/dubroxphpdebug/';
protected $printed_pretty_errors = 0;
protected $debug_level = 0;
protected $script_to_debug = 'all files';
protected $debug_cookie = 'no change';
protected $debug_discrete = false;
protected $debug_halt_on_err = 'no halt';
protected $error_log = '';
protected $bugs_collected = array();
protected $n_bugs_collected = 0;
protected $debugging_time /*= microtime(true)*/;
protected $debug_tools_dir;
protected $file_log_directory;
protected $debugger_var_name;
protected $debugger_commands;
function __construct( $debugger_options = false ) {
// default values
$this->debug_tools_dir = (isset($debugger_options['debug_tools_dir'])) ? $debugger_options['debug_tools_dir'] : '/dubrox_php_debugger/';
$this->debugger_var_name = (isset($debugger_options['debugger_var_name'])) ? $debugger_options['debugger_var_name'] : 'dpd';
$this->file_log_directory = (isset($debugger_options['file_log_directory'])) ? $debugger_options['file_log_directory'] : '';
// selects where to get infos about debugger commands
if( isset($_GET[$debugger_var_name]) ) {
$this->debugger_commands = $_GET[$this->debugger_var_name];
} elseif( isset($_POST[$this->debugger_var_name]) ) {
$this->debugger_commands = $_POST[$this->debugger_var_name];
} else {
$this->debugger_commands = isset($_REQUEST[$this->debugger_var_name]) ? $_REQUEST[$this->debugger_var_name] : '0';
}
// separates commands string in an array
$a_debug_commands = explode(',' , $this->debugger_commands);
// parses command array to set debugger variables
foreach($a_debug_commands as $command) {
switch($command) {
// enables cookie-driven PERSISTENT MODE (will maintain the debugger active until the cookie will be deleted)
case 'activate': case 'enable': case 'active': case 'turn on': case 'on': case 'yes': case 'create': case 'cookie': case 'permanent': case 'persistent': case 'persist':
$this->debug_cookie = 'on';
unset($_COOKIE[$this->debugger_var_name]);
setcookie($this->debugger_var_name, $this->debugger_commands, 0, '/');
break;
// disables cookie-driven PERSISTENT MODE (deletes debugger cookie)
case 'clear': case 'trun off': case 'off': case 'disable': case 'erase': case 'no': case 'delete': case 'cancel': case 'undo':
$this->debug_cookie = 'off';
unset($_COOKIE[$this->debugger_var_name]);
setcookie ($this->debugger_var_name, "", time() - 3600, '/');
break;
// prints out all errors only if a mayor error have been detected
case 'discrete':
$this->debug_discrete = true;
break;
// prints out all errors even if no error occurred
case 'indiscrete':
$this->debug_discrete = false;
break;
// ends script execution on any error of level superior to E_USER_NOTICE
// useful to avoid to run scripts that could potentially
// damage lot of data if badly invoked
case 'halt on error': case 'halt': case 'stop':
$this->debug_halt_on_err = 'yes';
break;
// keeps running even if an error occurred (whenever PHP allows to)
case 'no halt': case 'don\'t halt': case 'no stop': case 'don\'t stop':
$this->debug_halt_on_err = 'no';
break;
// gets debugging level
case '1': case 'fatal errors':
$this->debug_level = 1;
break;
case '2': case 'system errors':
$this->debug_level = 2;
break;
case '3': case 'user errors':
$this->debug_level = 3;
break;
case '4': case 'everything':
$this->debug_level = 4;
break;
// gets non-constant parameters
default:
// only collects errors generated by a specific script
if(strpos($command,'.')>0) {
$this->script_to_debug = $command;
}
break;
}
}
// if debugger level is superior to 0
// custom error handler is activated to collect errors
if($this->debug_level > 0) {
$this->error_log = '';
error_reporting(E_ALL);
// ob_start('before_ending') or trigger_error('Debug buffering failed!',E_USER_ERROR); // to test
$ex_error_handler = set_error_handler( array($this, 'errors_collector') );
if($ex_error_handler != NULL) {
trigger_error('Another error handler, named "'.$ex_error_handler.'", has been detected and substituted!');
}
}
}
// // to test
// function __destruct() {
// if($printed_pretty_errors == 0) {
// $close_body_tag = '</body>';
// ob_end_clean();
// $html_page = ob_get_clean();
// $pretty_errors = pretty_errors();
// echo str_replace($close_body_tag, $pretty_errors.$close_body_tag ,$html_page);
// } else {
// ob_end_flush();
// }
// }
// Custom error handler
// simply collects all errors and stores them in an internal variable
// without printing them
function errors_collector ($errno, $errstr, $errfile, $errline) {
// makes it easier to distinguish within error levels
switch($errno) {
case E_ERROR: $color='#FF0000'; $err_lvl='E_ERROR'; break;
case E_WARNING: $color='#FF9966'; $err_lvl='E_WARNING'; break;
case E_NOTICE: $color='#FFFF00'; $err_lvl='E_NOTICE'; break;
case E_USER_ERROR: $color='#FF0000'; $err_lvl='E_USER_ERROR'; break;
case E_USER_WARNING:$color='#FFFF00'; $err_lvl='E_USER_WARNING'; break;
case E_USER_NOTICE: $color='#888888'; $err_lvl='E_USER_NOTICE'; break;
default: $color='#ffffff'; $err_lvl='[unrecognized: '.$errno.']'; break;
}
// stores in a variable apart those errors which are of particular relevance (considered real bugs)
switch($errno) {
case E_ERROR: case E_WARNING: case E_PARSE: case E_NOTICE: case E_USER_ERROR: case E_USER_WARNING:
$this->bugs_collected[] = "Error: $errstr ([$err_lvl] in $errfile at $errline)";
$this->n_bugs_collected++;
break;
}
// saves the error only if it was generated by the script to debug
if( ($this->script_to_debug == 'all files') || ($this->script_to_debug == substr($errfile, -1*strlen($this->script_to_debug))) ) {
if( ($this->debug_level==1 && (($errno==E_ERROR) || ($errno==E_PARSE))) ||
($this->debug_level==2 && (($errno==E_ERROR) || ($errno==E_PARSE) || ($errno==E_WARNING))) ||
($this->debug_level==3 && (($errno==E_ERROR) || ($errno==E_PARSE) || ($errno==E_WARNING) || ($errno==E_NOTICE))) ||
($this->debug_level==4)) {
ob_start();
?>
<div style="border: 2px solid <?php echo $color?>;padding:2px;margin:5px;" title="Error level: <?php echo $err_lvl?>">
<p>File: <?php echo $errfile?> (at line #<?php echo $errline?>)</p> <?
if( stristr($errstr,'/>') != false ){
echo $errstr;
} else { ?>
<pre><?php echo htmlentities($errstr); ?></pre> <?
} ?>
</div>
<?php
$this->error_log .= ob_get_clean();
}
}
// halts script execution on error (if the corresponding flag has been set)
if( ($this->debug_halt_on_err == 'yes') && (
($errno==E_ERROR) || ($errno==E_PARSE) || ($errno==E_WARNING) || ($errno==E_NOTICE) || ($errno==E_USER_ERROR) || ($errno==E_USER_WARNING) ) ) {
pretty_errors('fatal_error_dump');
exit('HALT_ON_ERROR TRIGGERED: ['.$err_lvl.'] '.$errstr.' (on file '.$errfile.' line #'.$errline.')');
}
// flushes current output
flush();
return true;
}
// returns collected errors in a "pretty" way
// and adds some useful debug features for
function pretty_errors($output = 'return', $options = 'return debug infos') {
$this->printed_pretty_errors++;
if( $this->debug_level && ( ($this->debug_discrete == false) || ($this->n_bugs_collected > 0) ) ) {
$timestamp = date("YmdHis");
ob_start();
// includes CSS Diagnostics
?>
<link type="text/css" rel="stylesheet" id="estilo_de_seccion" href="<?php echo $this->debug_tools_dir; ?>css/css-diagnostics2.0.1.css" />
<?php
// includes Debugger JS
?>
<script type="text/javascript">
var debug = <?php echo $this->debug_level?>;
</script>
<script type="text/javascript" src="<?php echo $this->debug_tools_dir; ?>js/debug.js"></script>
<div id="errorsBox" style="font-family:sans-serif; width:90%; background-color: #FFE4E1; border: 5px solid #BB3030; margin: 30px auto 100px; text-align:left; font-size:11px; overflow:auto; padding:2px;">
<h1 style="font-size:20px; text-align:center;">Dubrox's PHP debugger <sup><a style="font-size:x-small; color:#aaa; text-decoration:none;" href="<?php echo Dubrox_PhpDebugger::site; ?>">v<?php echo Dubrox_PhpDebugger::version; ?></a></sup></h1>
<p style="text-align:center; font-weight:bold;">
time stamp: <?php echo $timestamp; ?> |
execution time (seconds): <?php echo microtime(true) - $this->debugging_time; ?></p>
<p style="text-align:center;">
# of bugs collected: <strong><?php echo $this->n_bugs_collected; ?></strong> |
Debug level: <strong><?php echo $this->debug_level; ?></strong> |
File to debug: <strong><?php echo $this->script_to_debug; ?></strong> |
Persistent mode: <strong><?php echo $this->debug_cookie; ?></strong> <?php echo (isset($_COOKIE[$this->debugger_var_name]))?'('.$_COOKIE[$this->debugger_var_name].')':''; ?> |
Halt on error: <strong><?php echo $this->debug_halt_on_err; ?></strong> |
Discrete mode: <strong><?php echo ($this->debug_discrete) ? 'on' : 'off'; ?></strong>
</p>
<?php echo $this->error_log; ?>
</div>
<?php
$html_errors = ob_get_clean();
// eventually writes a log file
if($output != 'return') {
// removes previous logs of that file
if( stristr($options, 'clear other logs') ) {
exec('rm '.$this->file_log_directory.$output.'*.html');
}
// rewrites always on the same log file
if( stristr($options, 'same file') ) {
$timestamp = '0';
}
// writes log
$output = $this->file_log_directory.$output.'-'.$timestamp.'.html';
file_put_contents($output , $html_errors);
}
if( stristr($options, 'return filename only') ) {
// only returns log filename
$return = $output;
} elseif( stristr($options, 'return bugs') ) {
// only returns bugs (errors of level superior to E_USER_NOTICE)
$return_str = implode("\n\n", $this->bugs_collected);
$return_str.= "\n\nError log file: $output";
$return = $return_str;
} else {
// returns "pretty" formatted errors
$return = $html_errors;
}
// resets bugs buffer and counters
if( stristr($options, 'clear buffer') ) {
$this->error_log = '';
$this->n_bugs_collected = 0;
$this->bugs_collected = array();
}
return $return;
}
return;
}
}
?>