<?php

/**
 * The Mega Error Handling.
 *
 * Copyright (C) 2007 Toby Inkster
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * @package demiblog
 * @subpackage Core
 * @author Toby Inkster <demiblog@tobyinkster.co.uk>
 * @copyright Copyright (C) 2007 Toby Inkster
 * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public Licence
 */

/* This is used in PHP<5.2 */
if (!function_exists('json_encode'))
    require_once 
'Services/JSON.php';

/**
 * Damn Nice Error Handling
 *
 * This class is able to output errors in a variety of styles, such as
 * HTML, e-mail and syslog. However, it's raison d'etre is to be able
 * to output errors as specially-formatted HTML comments, which can be
 * collected and manipulated by a nifty piece of Javascript and displayed
 * as an interactive console.
 */
class MegaErrorHandler
{
    const 
DEBUG_SILENT =   1;
    const 
DEBUG_HTML   =   2;
    const 
DEBUG_HTMLX  =   4;
    const 
DEBUG_JSON   =  32;
    const 
DEBUG_MAIL   =  64;
    const 
DEBUG_SYSLOG 256;

    private static 
$instance NULL;
    private 
$current_mime_type NULL;
    private 
$json_encoder NULL;
    private 
$queue = array();
    private static 
$debug_mode 1;
    
    private function 
__construct ()
    {
    }
    
    public function 
__clone ()
    {
        throw new 
Exception('MegaErrorHandler must not be cloned.');
    }
    
    public function 
__destruct ()
    {
        
$this->handle_queue();
    }
    
    
/**
     * Get instance of MegaErrorHandler.
     *
     * MegaErrorHandler is a singleton, so should not be constructed
     * directly. Call this method to get an instance.
     */
    
public function singleton ()
    {
        if (!isset(
self::$instance))
            
self::$instance = new MegaErrorHandler;
        return 
self::$instance;
    }
    
    
/**
     * Get/set the debugging mode of the handler.
     *
     * Use the defined class constants to set your preferred output mode. They
     * may be combined additively. May be called without any parameters to
     * avoid changing the mode at all.
     *
     * @param int $mode Preferred mode. Optional.
     * @return int Old mode.
     */
    
public static function set_debug_mode ($mode=NULL)
    {
        
$retval self::$debug_mode;
        if (isset(
$mode))
            
self::$debug_mode $mode;
        return 
$retval;
    }

    
/**
     * Call this function to start using this error handler.
     * 
     * May be passed a debugging mode so that you don't need to call
     * set_debug_mode seperately.
     *
     * @param int $mode Preferred mode. Optional.
     * @return void
     */
    
public static function set_error_handler ($mode=NULL)
    {
        
set_error_handler(MegaErrorHandler__error_handler);
        if (isset(
$mode))
            
self::set_debug_mode($mode);
    }
    
    
/**
     * Print out an error message if certain conditions are met.
     *
     * Will not print out the message if error_reporting doesn't match it.
     * Will delay the printing of errors until after headers have been sent.
     *
     * @param int $errno Error severity number
     * @param string $errstr Error message
     * @param string $errfile File in which error occurred
     * @param int $errline Line number of file which caused error
     * @param array $errcontext Context information
     * @return boolean Should error be considered handled?
     */ 
    
public function handle_error_conditional ($errno$errstr$errfile$errline$errcontext$bt)
    {
        if (!(
$errno error_reporting()))
            return 
TRUE;
        if (!
$this->sent_headers())
            return 
$this->enqueue($errno$errstr$errfile$errline$errcontext$bt);
        
$this->handle_queue();
        return 
$this->handle_error($errno$errstr$errfile$errline$errcontext$bt);
    }
    
    
/**
     * Check if PHP has sent HTTP header block yet.
     *
     * @return boolean Has it?
     */
    
private function sent_headers ()
    {
        return 
headers_sent();
    }
    
    
/**
     * Queue up an error message to be printed later.
     *
     * @param int $errno Error severity number
     * @param string $errstr Error message
     * @param string $errfile File in which error occurred
     * @param int $errline Line number of file which caused error
     * @param array $errcontext Context information
     * @return boolean Successfully queued?
     */ 
    
private function enqueue ($errno$errstr$errfile$errline$errcontext$bt)
    {
        
$this->queue[] = array($errno$errstr$errfile$errline$errcontext$bt);
        return 
TRUE;
    }
    
    
/**
     * Empty out queue of messages.
     *
     * Prints out any messages in the queue and removes them from it.
     *
     * @return bool Did anything actually need to be done?
     */ 
    
private function handle_queue ()
    {
        if (!
count($this->queue) || !$this->sent_headers())
            return 
FALSE;
        
        while (
$E array_shift($this->queue))
        {
            list (
$errno$errstr$errfile$errline$errcontext$bt) = $E;
            
$this->handle_error($errno$errstr$errfile$errline$errcontext$bt);
        }
        return 
TRUE;
    }
    
    
/**
     * Print out an error message unconditionally.
     *
     * This should never be called directly. Use the conditional function
     * instead.
     *
     * @param int $errno Error severity number
     * @param string $errstr Error message
     * @param string $errfile File in which error occurred
     * @param int $errline Line number of file which caused error
     * @param array $errcontext Context information
     * @return boolean Should error be considered handled?
     */ 
    
private function handle_error ($errno$errstr$errfile$errline$errcontext$bt)
    {
        for (
$i=0$i<1$i++)
            
array_shift($bt);

        
$errorType = array (
            
E_ERROR              => 'ERROR',
            
E_WARNING            => 'WARNING',
            
E_PARSE              => 'PARSING ERROR',
            
E_NOTICE             => 'NOTICE',
            
E_CORE_ERROR         => 'CORE ERROR',
            
E_CORE_WARNING       => 'CORE WARNING',
            
E_COMPILE_ERROR      => 'COMPILE ERROR',
            
E_COMPILE_WARNING    => 'COMPILE WARNING',
            
E_USER_ERROR         => 'USER ERROR',
            
E_USER_WARNING       => 'USER WARNING',
            
E_USER_NOTICE        => 'USER NOTICE',
            
E_STRICT             => 'STRICT NOTICE',
            
4096                 => 'RECOVERABLE ERROR'
            
);
                  
        
$E = array(
            
'E_NUMBER'  => $errno,
            
'E_TYPE'    => ucfirst(strtolower($errorType[$errno])),
            
'E_STRING'  => $errstr,
            
'E_FILE'    => $errfile,
            
'E_BASENAME'=> basename($errfile),
            
'E_LINE'    => $errline,
            
'E_CONTEXT' => print_r($errcontextTRUE),
            
'_SERVER'   => print_r($_SERVERTRUE),
            
'_GET'      => print_r($_GETTRUE),
            
'_POST'     => print_r($_POSTTRUE),
            
'_COOKIE'   => print_r($_COOKIETRUE),
            
'_ENV'      => print_r($_ENVTRUE),
            
'GLOBALS'   => print_r($GLOBALSTRUE),
            
'classes'   => print_r(get_declared_classes(), TRUE),
            
'interfaces'=> print_r(get_declared_interfaces(), TRUE),
            
'constants' => print_r(get_defined_constants(), TRUE),
            
'includes'  => print_r(get_included_files(), TRUE),
            
'ini'       => print_r(ini_get_all(), TRUE),
            
'extensions'=> print_r(get_loaded_extensions(), TRUE),
            
'backtrace' => print_r($btTRUE),
            
'source'    => highlight_file($errfileTRUE)
            );

        if (
strlen(session_id()))
            
$E['_SESSION'] = print_r($_SESSIONTRUE);

        if ((
self::DEBUG_JSON self::$debug_mode) && $this->is_html())
        {
            print 
'<!--ERROR ' 
                
str_replace('--','=-'$this->json_encode($E)) . 
                
" RORRE-->\n";
        }

        if ((
self::DEBUG_HTMLX self::$debug_mode) && $this->is_html())
        {
            
printf("<p><b>Error (%d)</b> at <tt>%s</tt><small>:%s</small><br>%s.</p>\n",
                
$errno$errfile$errline$errstr);
            print 
"<pre>\n";
            print 
htmlentities(print_r($ETRUE));
            print 
"</pre>\n";            
        }
        elseif ((
self::DEBUG_HTML self::$debug_mode) && $this->is_html())
        {
            
printf("<p><b>Error (%d)</b> at <tt>%s</tt><small>:%s</small><br>%s.</p>\n",
                
$errno$errfile$errline$errstr);
        }

        return 
TRUE;
    }
    
    
/**
     * Detects whether the currently output page is in HTML.
     *
     * Or rather, doesn't at the moment. Needs working on.
     *
     * @return bool TRUE.
     */
    
private function is_html()
    {
        return 
TRUE;
    }

    
/**
     * Detects whether the currently output page is in HTML.
     *
     * Or rather, doesn't at the moment. Needs working on.
     *
     * @return bool TRUE.
     */
    
private function json_encode ($struct)
    {
        
/* PHP 5.2 includes JSON abilities natively. If available
         * then use them, because they're really fast.
         */
        
if (function_exists('json_encode'))
            return 
json_encode($struct);
    
        
/* We need to support earlier versions of PHP 5.x though, so try
         * using PEAR library too.
         */
        
if (isset($this->json_encoder) && ($this->json_encoder instanceof Services_JSON))
            return 
$this->json_encoder->encode($struct);
        
        
$this->json_encoder = new Services_JSON;
        return 
$this->json_encoder->encode($struct);
    }    
}

/**
 * This function should be MegaErrorHandler::error_handler().
 *
 * Seems to be a bug in PHP 5.0.x (and possibly other versions of PHP) in dealing
 * with error handlers which are static class methods. It doesn't always seem to
 * cause a problem, but does so enough for it to *be* a problem.
 *
 * Anyway, this is the function which should be made the error_handler. You shouldn't
 * use set_error_handler directly. Call MegaErrorHandler::set_error_handler()
 * instead.
 *
 * @param int $errno Error severity number
 * @param string $errstr Error message
 * @param string $errfile File in which error occurred
 * @param int $errline Line number of file which caused error
 * @param array $errcontext Context information
 * @return boolean Should error be considered handled?
 */
function MegaErrorHandler__error_handler ($errno$errstr$errfile$errline$errcontext)
{
    if (!isset(
$errfile))
        
$errfile '';
    if (!isset(
$errline))
        
$errline 0;
    if (!isset(
$errcontext))
        
$errcontext = array();

    
$eh MegaErrorHandler::singleton();
    return 
$eh->handle_error_conditional(    $errno,
                        
$errstr,
                        
$errfile
                        
$errline,
                        
$errcontext,
                        
debug_backtrace()
                        );
}

/**
 * Quick debugging function.
 */
function barf ($param)
{
    if (
is_array($param))
        
$param print_r($paramTRUE);
    if (
class_exists('DemiblogException'))
        throw new 
DemiblogException($param);
    throw new 
Exception($param);
}

?>