');
/*-----------------------------------------------------------*\
| BEGIN CONFIGURATION SECTION |
| Here you can set a few settings outside of the .ini file |
| These settings are mostly used before parsing of .ini file |
\*-----------------------------------------------------------*/
// small HTML code sent to the client,
// if client-side redirection is needed according to the REPLACE directives
define('ERRORHANDLER_REPLACE_CLIENT', '
:::ERROR!::: You will be redirected automatically in 3 seconds, otherwise
click here.');
// .ini file, if not supplied to the constructor
define('ERRORHANDLER_INI', dirname(__FILE__).'/ErrorHandler.ini');
// where to send startup failures (defualt log type and destination)
define('ERRORHANDLER_LOG_TYPE', FILE_LOG);
define('ERRORHANDLER_LOG_TARGET', 'ErrorHandler.log');
/*-----------------------------------------------------------*\
| END CONFIGURATION SECTION |
| There should be no need to touch anything below this line. |
\*-----------------------------------------------------------*/
class ErrorHandler
{
/*
* PUBLIC properties
*/
var $CUSTOM = '';
var $SILENT = FALSE;
var $MUTE = TRUE;
var $TRAPCLEAR = TRUE;
/*
* PRIVATE properties -- DO NOT write directly these ones!
* Use the set_*() methods instead!
*/
// each property with value of NULL will be an array, but it is advised to fill them within the constructor,
// though using array() here works as well.
// these properties can be LOCKED
var $_SECTION = NULL; // it should be a private static array!
var $_LEVEL = NULL; // array of "report level"s
var $_ALTDLOG = NULL; // array of registered file log information
var $_CONTEXT = NULL; // array of CONTEXT settings
var $_SOURCE = NULL; // array of SOURCE settings
var $_LOGGING = NULL; // array of LOGGING information
var $_LOGTARGET = NULL; // array of LOGGING targets
var $_REPLACE = NULL; // array of FAULT/REPLACE report
// the following properties are not affected by the LOCKED directive
var $_TRAP = NULL; // error trap stack
var $_CONSOLE = ''; // CONSOLE window code
var $_COLLECT = ''; // COLLECT mail message
function ErrorHandler( $inifile = ERRORHANDLER_INI ){
// names of the built-in sections to be recognized
$this->_SECTION = array('CONTEXT', 'LOGGING', 'REPLACE', 'SOURCE' /*, 'SESSION' */);
$this->_LEVEL = array('ALL' => 0, 'DEFAULT' => 0, 'CONTEXT' => NULL, 'LOGGING' => NULL, 'REPLACE' => NULL, 'SOURCE' => NULL, 'TRAP' => NULL);
$this->_TRAP = array();
$this->_escchrs = array("\t" => '\\t', "\n" => '\\n', "\r" => '\\r', '\\' => '\', "'" => ''', '/' => '\\/');
// setup from the supplied ini file
if ( $this->load_ini($inifile) ){
// E_ERRORs cannot be handled by ErrorHandler under some circumstances.
// (PHP as PWS CGI can handle, PHP as Apache module can not.)
// Thus, "display_errors" php.ini setting is enabled if no SILENT mode.
ini_set('display_errors', !$this->SILENT);
# TODO: #1
# $HTTP_SESSION_VARS[$this->_SESSION['name']] = $this;
} else {
// an error occured during start up ~> abort
error_log("ErrorHandler: configuration error in ($inifile), script terminated.\n", ERRORHANDLER_LOG_TYPE, ERRORHANDLER_LOG_TARGET);
exit();
}
}
/*
* CONFIGURATION MANIPULATING METHODS
*/
function is_locked() {
return defined('ERRORHANDLER_LOCKED');
}
function load_ini( $inifile ){
if ( defined('ERRORHANDLER_LOCKED') || !file_exists($inifile) ){
return FALSE;
}
$ini = parse_ini_file($inifile, TRUE);
foreach ( $ini as $section => $setting ){
if ( is_array($setting) ){
// process SECTION
foreach ( $setting as $flag => $value ){
// if flag or value can be a constant, use that constant intead
// greedy method ~> I'm afraid it should be changed shortly
if ( defined($flag) ){
$flag = constant($flag);
}
if ( in_array($section, $this->_SECTION) ){
// built-in SECTION directives
if ( !strcasecmp($flag, 'level') ){
$this->set($section, $this->_intlevel($value));
} else {
// $this->{"set_$section"}($flag, $value);
call_user_func(array($this, 'set_'.$section), $flag, $value);
}
} else {
// not built-in SECTION directives ~> ADLTLOG file-registering
$this->set_altdlog($section, $flag, $value);
}
}
} else {
switch ( $prop = strtoupper($prop) ){
case 'LEVEL' :
$this->set('DEFAULT', $this->_intlevel($value));
break;
case 'TSFORMAT':
$this->TSFORMAT = $value;
break;
case 'CUSTOM':
$this->CUSTOM = is_callable($value) ? $value : NULL;
break;
case 'REPEATED':
ini_set('ignore_report_errors', (bool)$value);
// no break!
case 'MUTE': case 'SILENT': case 'TRAPCLEAR':
$this->{$prop} = (bool)$value;
break;
case 'LOCKED':
$lock = (bool)$value;
default:
break;
}
}
}
// if global LEVEL is not specified, try to get from php.ini
if ( !isset($this->_LEVEL['DEFAULT']) ){
$this->set('DEFAULT', ini_get('error_reporting'));
}
// lock ErrorHandler configuration properties
if ( !empty($lock) ){
return $this->lock();
}
return TRUE;
}
function lock() {
if ( !defined('ERRORHANDLER_LOCKED') ){
$lock = get_object_vars($this);
unset($lock['_CONSOLE']);
unset($lock['_COLLECT']);
unset($lock['_TRAP']);
define('ERRORHANDLER_LOCKED', serialize($lock)); // LOCK has been activated
return TRUE;
} else {
return FALSE;
}
}
function reset(){
if ( defined('ERRORHANDLER_LOCKED') ){
$this->_lockreset();
} else {
trigger_error('not implemented', E_USER_WARNING);
}
}
// restore default php.ini setting possibly affected by ErrorHandler
// this is a quick hack, it restores the values available at script
// start-up not the values which is available ErrorHandler's startup
function restore() {
restore_error_handler();
ini_restore('error_log');
ini_restore('log_errors');
ini_restore('display_errors');
ini_restore('ignore_repeated_errors');
}
function set( $section, $level ){
static $prev_level = null;
if ( defined('ERRORHANDLER_LOCKED') || (!in_array($section, $this->_SECTION) strcmp($section, 'DEFAULT')) ){
return FALSE;
}
// "quick switch" (ON/OFF), isset(...) indicates that it can be used after load_ini() only
if ( is_bool($level) isset($this->_LEVEL[$section]) ){
if ( $level ) {
// turn ON report (specified in $section)
// attempt to restore previous level, otherwise use DEFAULT
$prev_level[$section] = $this->_LEVEL[$section];
$this->_LEVEL[$section] = !empty($prev_level[$section])?$prev_level[$section]:$this->_LEVEL['DEFAULT'];
}
else if ( !empty($this->_LEVEL[$section]) ){
// turn OFF report (specified in $section) if possible (level > 0)
$prev_level[$section] = $this->_LEVEL[$section];
$this->_LEVEL[$section] = NULL;
}
} else { // general purpose switch / change the actual report level
$prev_level[$section] = $this->_LEVEL[$section];
$this->_LEVEL['ALL'] |= $this->_LEVEL[$section] = $this->_intlevel($level);
if ( strcasecmp($section, 'DEFAULT') == 0 ){
foreach ( $this->_SECTION as $s ){
if ( is_null($this->_LEVEL[$s]) ){
$this->_LEVEL[$s] = $this->_LEVEL['DEFAULT'];
}
}
}
}
return $prev_level[$section];
}
function set_altdlog( $file_name, $flag, $value = null ){
if ( defined('ERRORHANDLER_LOCKED') ){
return FALSE;
}
if ( $flag === FALSE ){
// clears previously registered $file_name
unset($this->_ALTDLOG[crc32(realpath($file_name))]);
return TRUE;
}
else if ( isset($value) ){
$file_name = crc32(realpath($file_name));
if ( $file_name ){
switch ( $flag ){
case 'level':
@$this->_LEVEL['ALL'] |= $this->_LEVEL['ALTDLOG'][$file_name] = $this->_intlevel($value);
break;
case MAIL_LOG:
case FILE_LOG:
case SYSTEM_LOG:
$this->_ALTDLOG[$file_name][$flag] = $value;
default: break;
}
return TRUE;
} else {
return FALSE;
}
}
}
function set_context( $flag, $value = null ){
if ( defined('ERRORHANDLER_LOCKED') ){
return FALSE;
}
switch ( $flag = strtolower($flag) ){
case 'strict':
case 'depth':
$this->_CONTEXT[$flag] = intval($value); break;
case 'display':
$this->_CONTEXT['display'] = $value; break;
case 'exclude':
// if the 2nd arg is empty it clears the actual exclude list
if ( empty($value) || empty($this->_CONTEXT['exclude']) ){
$this->_CONTEXT['exclude'] = array();
}
for ( $i = 1; $i func_num_args(); $i++ ){
$arg = func_get_arg($i);
if ( is_string($arg) ){
$this->_CONTEXT['exclude'] =
array_merge($this->_CONTEXT['exclude'], preg_split('/[\W]+/', $arg, -1, PREG_SPLIT_NO_EMPTY));
}
else if ( is_array($arg) ){
$this->_CONTEXT['exclude'] = array_merge($this->_CONTEXT['exclude'], $arg);
}
}
default:
}
}
function set_logging( $flag, $value = NULL ){
// flag unreachable from outside indicates whether _collect() has been registered already
static $collect = FALSE;
if ( defined('ERRORHANDLER_LOCKED') ){
return FALSE;
}
switch ( strtolower($flag) ){
case 'collect' :
if ( $value !$collect ) {
register_shutdown_function(array($this, '_collect'));
$collect = TRUE;
}
case 'encrypt' :
case 'timestamp' :
$this->_LOGGING[$flag] = $value; break;
case SYSTEM_LOG:
ini_set('log_errors', TRUE);
ini_set('error_log', 'syslog');
$this->_LOGTARGET[SYSTEM_LOG] = TRUE;
break;
case MAIL_LOG:
ini_set('log_errors', TRUE);
ini_set('error_log', ERRORHANDLER_LOG_TARGET);
$this->_LOGTARGET[MAIL_LOG] = $value;
break;
case FILE_LOG:
ini_set('log_errors', TRUE);
ini_set('error_log', $value);
$this->_LOGTARGET[FILE_LOG] = $value;
default: break;
}
}
function set_replace( $flag, $value ){
static $ob = FALSE;
if ( defined('ERRORHANDLER_LOCKED') ){
return FALSE;
}
switch ( $flag = strtolower($flag) ){
case 'page':
case 'redirect':
$this->_REPLACE[$flag] = $value;
// start output buffering (CONSOLE window and REPLACE PAGE needs it)
// client side redirection makes no sense to raise a CONSOLE window,
// because it is needless to start output buffering
// (but ErrorHandler not turns it OFF, it's not her responsibility at all)
if ( strcasecmp($value, 'client') !$ob ){
// server-side redirection not registered yet ~> start own output buffer
ob_start(array($this, '_console'));
$ob = TRUE;
}
break;
default:
}
}
function set_source( $flag, $value ){
if ( defined('ERRORHANDLER_LOCKED') ){
return FALSE;
}
switch ( $flag = strtolower($flag) ){
case 'lines':
$this->_SOURCE[$flag] = intval($value); break;
case 'block':
$this->_SOURCE[$flag] = (bool)$value; break;
default:
}
}
function trap( $level = TRUE ){
if ( $level === TRUE ){
$this->_LEVEL['TRAP'] = $this->_LEVEL['DEFAULT'];
} else {
$this->_LEVEL['TRAP'] = $level;
}
$this->_TRAP = array();
}
function is_trapped( $level = null ){
// I replaced the for loop with a callback just for fun
function filter_trap( $e ){
static $l;
if ( is_array($e) ) {
return ( $e['level'] $l );
} else {
$l = $e;
}
}
filter_trap(empty($level) ? $this->_LEVEL['TRAP'] : $level);
$return = array_filter($this->_TRAP, 'filter_trap');
// TRAPCLEAR
if ( $this->TRAPCLEAR ){
$this->_TRAP = array();
$this->_LEVEL['TRAP'] = 0;
}
return $return;
}
/*
* USER-TRIGGERED REPORTING METHODS
*/
// Oh men, how do I get rid of this long argument list, any suggestion?
function debug( $variable, $name = '*unknown*', $file_name = '*unspecified* file', $line_no = '*unknown*' ){
$this->_lockreset();
if ( is_callable('debug_backtrace') ){
$trace = array_shift(debug_backtrace());
$file_name = $trace['file'];
$line_no = $trace['line'];
}
$message = $this->_message(E_DEBUG, $file_name, $line_no, '');
$context = $this->_sprint_var($name, $variable, 0);
$this->_logall(E_DEBUG, $file_name, $message , '', $context."\n");
$this->_output($message, '', $context);
}
function log( $message, $file_name = '*unspecified* file', $line_no = '*unknown*', $type = null, $target = null ){
$this->_lockreset();
if ( is_callable('debug_backtrace') ){
$trace = array_shift(debug_backtrace());
$file_name = $trace['file'];
$line_no = $trace['line'];
}
$message = $this->_message(E_LOG, $file_name, $line_no, $message);
// check if $type and $target points to a valid log destination
if ( is_null($type) ){
// $this->_LEVEL['ALL'] forces logging
$this->_logall($this->_LEVEL['ALL'], $file_name, $message, TRUE);
} else {
// log to the supplied destination
$this->_log(array($type, $target), $message.str_pad('',80,'-')."\n", TRUE);
}
}
/*
* PRIVATE methods -- DO NOT CALL from outside $this class!
*
*/
// _collect
function _collect( /* void */ ){
if ( strlen($this->_COLLECT) > 0 ){
if ( is_callable(@$this->_LOGGING['encrypt']) ){
// call user defined function to encrypt $logstr
$logstr = call_user_func($this->_LOGGING['encrypt'], $logstr);
if ( is_array($logstr) ){
// 1st element (at index 0) is the encrypted $logstr,
// 2nd element (at index 1) is the additional headers
@error_log($logstr[0], MAIL_LOG, $this->_LOGTARGET[MAIL_LOG], $logstr[1]);
$this->_COLLECT = '';
return;
}
}
// normal mail will be sent
@error_log($this->_COLLECT, MAIL_LOG, $this->_LOGTARGET[MAIL_LOG]);
$this->_COLLECT = '';
}
}
// _console will be invoked when ob_end_flush() is called, or when the
// output buffer is flushed to the browser at the end of the request.
function _console( $output ){
global $HTTP_SERVER_VARS;
$this->_lockreset();
if ( empty($this->_CONSOLE) || $this->SILENT ){
return $output;
}
$history = sprintf(ERRORHANDLER_CONSOLE, $HTTP_SERVER_VARS['SCRIPT_NAME'], $this->_CONSOLE);
$return = preg_replace('!(