<?php

/*
** Fishlib - a collection of utilities for db-driven applications in PHP
** Copyright (C) 2002  LTWD, LLC DBA The Madfish Group
**
** This library is free software; you can redistribute it and/or
** modify it under the terms of the GNU Lesser General Public
** License as published by the Free Software Foundation; either
** version 2.1 of the License, or (at your option) any later version.
**
** This library 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
** Lesser General Public License for more details.
**
** You should have received a copy of the GNU Lesser General Public
** License along with this library; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

// create constants to represent the three places that need to know
// how to handle a given error level - normal error reporting,
// error logging, and debugging
if (!defined('H_ERROR'))
    
define('H_ERROR',1);
if (!
defined('H_LOG'))
    
define('H_LOG',2);
if (!
defined('H_DEBUG'))
    
define('H_DEBUG',4);
if (!
defined('H_ALL'))
    
define('H_ALL', (H_ERROR H_LOG H_DEBUG));

require_once(
'fishutil.class.php');
class 
FishError
{
    function 
error_levels($error_level=NULL,$default=FALSE)
    {
        static 
$error_levels NULL;
        if (
$error_levels === NULL)
        {
            
$error_levels = array(
            
E_ERROR => 'E_ERROR'
            
E_WARNING => 'E_WARNING'
            
E_PARSE => 'E_PARSE'
            
E_NOTICE => 'E_NOTICE'
            
E_CORE_ERROR => 'E_CORE_ERROR'
            
E_CORE_WARNING => 'E_CORE_WARNING'
            
E_COMPILE_ERROR => 'E_COMPILE_ERROR'
            
E_COMPILE_WARNING => 'E_COMPILE_WARNING'
            
E_USER_ERROR => 'E_USER_ERROR'
            
E_USER_WARNING => 'E_USER_WARNING'
            
E_USER_NOTICE => 'E_USER_NOTICE'
            
E_ALL => 'E_ALL'
        
);
        }
        if (
$error_level === NULL)
            return 
$error_levels;
        if (empty(
$error_levels[$error_level]))
            return 
$default;
        return 
$error_levels[$error_level];
        return 
$error_levels;
    }

    function 
trace_list($what=NULL$status=NULL)
    {
        
error_log(__FUNCTION__.':Fix this reference - use FishDebug');
        require_once(
'fishdebug.class.php');
        return 
FishDebug::trace_list($what$status);
    }

    function 
start_trace($file=NULL)
    {
        
error_log(__FUNCTION__.':Fix this reference - use FishDebug');
        require_once(
'fishdebug.class.php');
        return 
FishDebug::start_trace($file);
    }

    function 
stop_trace($file=NULL)
    {
        
error_log(__FUNCTION__.':Fix this reference - use FishDebug');
        require_once(
'fishdebug.class.php');
        return 
FishDebug::stop_trace($file);
    }

    function 
trace()
    {
        
error_log(__FUNCTION__.':Fix this reference - use FishDebug');
        require_once(
'fishdebug.class.php');
        
$args func_get_args();
        return 
call_user_func_array(array('FishDebug''trace'), $args);
    }

    function 
watch($files=array(),$direction=TRUE)
    {
        
error_log(__FUNCTION__.':Fix this reference - use FishDebug');
        require_once(
'fishdebug.class.php');
        return 
FishDebug::stop_trace($files,$direction);
    }

    function 
unwatch($files=array())
    {
        
error_log(__FUNCTION__.':Fix this reference - use FishDebug');
        require_once(
'fishdebug.class.php');
        return 
FishDebug::watch($filesFALSE);
    }

    function 
dumpvar($var, &$vlist,$tick=0)
    {
        
error_log(__FUNCTION__.':Fix this reference - use FishDebug');
        require_once(
'fishdebug.class.php');
        return 
FishDebug::watch($var$vlist$tick);
    }

    function 
debug_file()
    {
        
// store the file names for which we set up debugging levels
        
static $debug_files = array();

        
// some quick & dirty argument handling. we can do this
        // because we're only interested in two possible arguments,
        // $file and $level

        
$args func_get_args();
        
$file NULL;
        
$level NULL;
        foreach (
$args as $arg)
        {
            switch (
gettype($arg))
            {
                case 
'array':
                case 
'object':
                    
extract((array)$arg,EXTR_IF_EXISTS);
                    break;
                case 
'string':
                    
$file $arg;
                    break;
                case 
NULL:
                    break;
                default:
                    
$level = (int)$arg;
            }
        }

        if (!
$file)
        {
            
// if no file name is given, use the current file by default.

            // we want to find the path to the file that called this function,
            // so we can't use __FILE__ - that'll just give us the name of
            // the file where this function is defined. use debug_backtrace()
            // instead.
            
$backtrace debug_backtrace();
            
$last array_shift($backtrace);
            
$file $last['file'];

            if (
$level === NULL)
            {
                
// if we're using a default file name, then if no level
                // is passed in, set up a default level as well
                
$level FishError::get_constant($file);
            }
        }
        else
        {
            
$rfile realpath($file);
            if (
$rfile)
            {
                
$file $rfile;
            }
        }

        if (
$level !== NULL)
        {
            
// if we're given a level for a file (or have set one
            // as a default case), make a record of it
            
$debug_files[$file] = $level;
        }

        
// in any case, return the current level for this file,
        // if there is one, or FALSE if there isn't

        
if (array_key_exists($file$debug_files))
        {
            return 
$debug_files[$file];
        }
        
// so now do more tedious file name matching
        
$pat '/'.str_replace('/','.',$file).'/i';
        return 
current(preg_grep($patarray_keys($debug_files)));
    }

    function 
error_debugging($newlevel=NULL)
    {
        static 
$debug_error_level 0;
        
$output $debug_error_level;
        if (
$newlevel !== NULL)
        {
            
$debug_error_level $newlevel;
        }
        return 
$output;
    }

    function 
error_logging($newlevel=NULL)
    {
        static 
$log_error_level E_ALL;
        
$output $log_error_level;
        if (
$newlevel !== NULL)
        {
            
$log_error_level $newlevel;
        }
        return 
$output;
    }

    function 
check_length($a$len=0)
    {
        foreach (
$a as $k => $v)
        {
            if (
is_array($v))
                
FishError::check_length($v);
            elseif (
is_object($v))
                
FishError::check_length(get_object_vars($v));
            else
                echo 
'<li>k='var_dump($kTRUE), ' strlen(v)='strlen($v), "\n";
        }
    }

    function 
error_handler($error_level,$error_msg,$file,$line,$context)
    {
        
// $context is an array of all the variables defined at the
        // time of the error. so we can check it to see if the
        // variables $public_error, $private_error, or $debug were defined.

        
if ($error_msg == 'checklength')
        {
            
FishError::check_length($context48);
            return;
        }

        switch (
$error_level)
        {
            
// the non-fatal errors
            
case E_NOTICE:
            case 
E_USER_NOTICE:
            case 
E_WARNING:
            case 
E_USER_WARNING:
                
$exit_at_end FALSE;

            
// everything else is fatal
            
default:
                
$exit_at_end TRUE;
        }

        if (
$exit_at_end)
        {
            require_once(
'fishdb.class.php');
            
FishDB::attempt_rollback();
        }

        if (
array_key_exists('public_error'$context))
        {
            
$public_error $context['public_error'];
        }
        else
        {
            
$public_error $error_msg;
        }
        if (
array_key_exists('private_error'$context))
        {
            
$private_error $context['private_error'];
        }
        else
        {
            
$private_error '';
        }

        
// the value for $debug that we'll use is a combination of the
        // value of the $debug variable in the scope of the line where
        // the error occurred (if defined), the setting of the debugging 
        // level for the file from debug_file() (if there is one), and 
        // the value of the constant DEBUG (if defined). 

        
if (array_key_exists('debug'$context))
        {
            
$debug_scope $context['debug'];
        }
        else
        {
            
$debug_scope $error_level;
        }
        
$debug_file FishError::debug_file($file);
        if (
defined('DEBUG'))
        {
            
$debug_constant constant('DEBUG');
        }
        else
        {
            
$debug_constant 0;
        }

        
$debug = ($debug_scope $debug_file) | $debug_constant;

        
// get the current error handling levels
        
$error_reporting error_reporting();
        
$error_logging FishError::error_logging();
        
$error_debugging FishError::error_debugging();

        
// get the name of the constant that matches the error 
        // (if there is one)
        
$error_name FishError::error_levels($error_level"_Error #$error_level");
        
$public_name substr($error_name,strrpos($error_name,'_')+1);

        
// write the error to the server error log if it's of a level
        // that we're interested in 

        
if ($error_logging $error_level)
        {
            if (
preg_match('/^trace<ul>/'$error_msg))
            {
                
$logerror $error_msg;
            }
            else
            {
                
$logerror "$error_name file: $file line: $line\n"
                    
."    error: $error_msg\n"
                
;
            }
            if (
$public_error && $public_error != $error_msg)
            {
                
$logerror .= "    public_error: $public_error\n";
            }

            if (
$private_error && $private_error != $error_msg
                
&& $private_error != $public_error
            
)
            {
                
$logerror .= "    private_error: $private_error\n";
            }

            
error_log($logerror);
        }

        
// if $debug is set to something that we're debugging at the
        // moment, add some stuff to the error message and make sure
        // it gets displayed, no matter what the error_reporting level is

        
if ($error_debugging $debug && !preg_match('/^trace:<ul>/'$public_error))
        {
            
$debug_error " <li>error: $error_msg\n";
            if (
$public_error && $public_error != $error_msg)
            {
                
$debug_error .= " <li>public_error: $public_error\n";
            }
            if (
$private_error && $private_error != $error_msg
                
&& $private_error != $public_error
            
)
            {
                
$debug_error .= " <li>private_error: $private_error\n";
            }

            
$debug_error .= "<li>backtrace:<ul>\n";

            
$backtrace debug_backtrace();
            if (
array_key_exists('skip_past_function'$context))
            {
                
$skip_past_function $context['skip_past_function'];
            }
            
$goodtrace = array();
            foreach (
$backtrace as $skip)
            {
                if (isset(
$skip_past_function))
                {
                    if (
$skip['function'] == $skip_past_function)
                    {
                        unset(
$skip_past_function);
                    }
                    continue;
                }
                
$goodtrace[] = $skip;
            }
            if (
count($goodtrace) > 0)
                
$backtrace $goodtrace;

            foreach (
$backtrace as $skip)
            {
                
$class 'NoClass';
                
$function 'NoFunction';
                
$file 'NoFile';
                
$line 'NoLine';
                
extract($skipEXTR_IF_EXISTS);
                
$debug_error .= sprintf("\t<li>%s::%s [%s:%s]\n"
                    
$class
                    
$function
                    
$file
                    
$line
                
);
            }
            
$debug_error .= "</ul>\n";

            
$public_error .= $debug_error;

            
// if E_ALL has been explicitly set in the debug mask
            // dump *everything*...
            
if (($error_debugging E_ALL) == E_ALL)
            {
                
// OK, not everything. but you can uncomment this if you want.
                // $context = array_merge($context,get_defined_constants());
                
require_once('fishdebug.class.php');
                
$public_error .= "<li>context:".FishDebug::dumpvar($context,$ignoreme)."\n";
            }
        }
        elseif (!(
$error_reporting $error_level))
        {
            
// if the error is not of a level that we're reporting,
            // blank out the error message
            
$public_error '';
        }

        if (!empty(
$public_error) && ini_get('display_errors'))
        {
            if (
preg_match('/^trace:<ul>/'$public_error))
            {
                print 
$public_error;
            }
            else
            {
                print 
"
    <blockquote>
    <b>$public_name:</b>
    $public_error
    </blockquote>
    "
;
            }
        }

        if (
FishError::error_debugging() & FishError::get_constant('fatal'))
        {
            exit;
        }

        if (
$exit_at_end)
        {
            exit;
        }
        else
        {
            return;
        }
    }

    function 
set_handler($newvalue=NULL$where=NULL$direction=NULL)
    {
        
// store the names of the handling functions 
        
static $functions NULL;
        if (
$functions === NULL)
        {
            
$functions = array(
                
H_ERROR => 'error_reporting'
                
H_LOG => 'error_logging'
                
H_DEBUG => 'error_debugging'
            
);
        }
        
// this will hold the last error level that we turned on,
        // so we can easily turn it off (see below)
        
static $last_args = array();

        if (
$direction === FALSE && $newvalue === NULL && $where === NULL)
        {
            
// if we just get an argument to turn something off, but
            // not what or where, use the last error level that we
            // turned on
            
list($where,$newvalue) = array_pop($last_args);
        }

        if (empty(
$where))
        {
            
// if we don't get a request for a specific kind of
            // handler, pick a default one
     
            
if (FishError::error_levels($newvalue))
            {
                
// if the error level we're dealing with
                // is one of the standard PHP values, assume that
                // we want to change error handling

                
$where H_ERROR H_LOG;
            }
            else
            {
                
// if we're dealing with some made-up error level,
                // it's probably for debugging, so use that as
                // the default

                
$where H_DEBUG;
            }
        }

        if (
$direction !== FALSE)
        {
            
// if we're turning on handling for something, store
            // it for turning off later

            
array_push($last_args, array($where,$newvalue));
        }

        
$output 0;
        foreach (
$functions as $handler => $handler_function_name)
        {
            if (
$where $handler)
            {
                
// if this type of handler is one of the ones we want
                // to change, get its current value

                
if (function_exists($handler_function_name))
                    
$call_arg $handler_function_name;
                elseif (
in_array(strtolower($handler_function_name), get_class_methods(__CLASS__)))
                    
$call_arg = array(__CLASS__,$handler_function_name);

                
$handler_level call_user_func($call_arg);

                
// either set handling directly to new level, or turn it on or off
                
if ($direction === FALSE)
                {
                    
$handler_level $handler_level $newvalue;
                }
                elseif (
$direction === TRUE)
                {
                    
$handler_level $handler_level $newvalue;
                }
                else 
                {
                    
$handler_level $newvalue;
                }

                
// add the new level to our result
                
$output $output $handler_level;

                
// call the handler function to set it to the new level
                
call_user_func($call_arg,$handler_level);
            }
        }

        
// return an OR'd sum of the changed handling values
        
return $output;

    }

    function 
push_handler($newvalue=0$where=NULL)
    {
        return 
FishError::set_handler($newvalue,$where,TRUE);
    }

    function 
pop_handler($newvalue=NULL$where=NULL)
    {
        return 
FishError::set_handler($newvalue,$where,FALSE);
    }

    function 
whereami($fields=NULL)
    {
        
$bt debug_backtrace();
        
array_shift($bt);
        if (
$fields === NULL)
        {
            
$fields = array('class','function','file','line');
        }
        
$output '';
        foreach (
$bt as $f)
        {
            
$output .= '<li>'
                
implode('::'array_key_value($f,$fields,NULL,'list'))
                . 
"\n";
        }
        return 
$output;
    }

    function 
get_constant($constname='')
    {
        
// start at one above E_USER_NOTICE to avoid conflicts
        // (we can't initialize a static variable to an expression,
        // so we have to start it off as NULL and then fix that.)

        
static $last_constant NULL;
        if (
$last_constant === NULL)
        {
            
$last_constant E_USER_NOTICE << 1;
        }
        static 
$defined_constants = array();
        static 
$defined_or 0;

        
$output 0;
        if (!empty(
$constname))
        {
            if (!
defined($constname))
            {
                
define($constname,$last_constant);
                
$defined_constants[$constname] = $last_constant;
                
$defined_or $defined_or $last_constant;
                
$last_constant $last_constant << 1;
            }
            
$output constant($constname);
        }
        else
        {
            
// if no constant name is given, hand back the equivalent 
            // of E_ALL for the constants defined so far
            
$output $defined_or;
        }
        return 
$output;
    }
}

set_error_handler(array('FishError''error_handler'));
?>