<?php
/**
 * patErrorManager main error management class used by pat tools for the
 * application-internal error management. Creates patError objects for
 * any errors for precise error management.
 *
 *	$Id: patErrorManager.php 12694 2009-09-11 21:03:02Z ian $
 *
 * @package	patError
 */

/**
 * error definition: illegal options.
 */
define( 'PATERRORMANAGER_ERROR_ILLEGAL_OPTIONS', 1 );

/**
 * error definition: callback function does not exist.
 */
define( 'PATERRORMANAGER_ERROR_CALLBACK_NOT_CALLABLE', 2 );

/**
 * error definition: illegal error handling mode.
 */
define( 'PATERRORMANAGER_ERROR_ILLEGAL_MODE', 3 );


/**
 * global definitions needed to keep track of things when calling the patErrorManager
 * static methods.
 */
$GLOBALS['_pat_errorHandling']	=	array(
											E_NOTICE	=> array( 'mode' => 'echo' ),
											E_WARNING	=> array( 'mode' => 'echo' ),
											E_ERROR		=> array( 'mode' => 'die' )
										);

/**
 * available error levels
 * Stored in a variable to keep them flexible
 */
$GLOBALS['_pat_errorLevels']	=	array(
											E_NOTICE	=> 'Notice',
											E_WARNING	=> 'Warning',
											E_ERROR		=> 'Error'
										);
/**
 * error class names
 * Stored in a variable allows to change during runtime
 */
$GLOBALS['_pat_errorClass']	=	'patError';

/**
 * ignore errors
 * Store error-codes that will be ignored forever
 */
$GLOBALS['_pat_errorIgnores']	=	array();

/**
 * expects errors
 * Store error-codes that will be ignored once
 */
$GLOBALS['_pat_errorExpects']	=	array();


/**
 * patErrorManager main error management class used by pat tools for the
 * application-internal error management. Creates patError objects for
 * any errors for precise error management.
 *
 * @static
 * @package	patError
 * @version	0.3
 * @author	gERD Schaufelberger <gerd@php-tools.net>
 * @author	Stephan Schmidt <schst@php-tools.net>
 * @license	LGPL
 * @link	http://www.php-tools.net
 * @todo	implement ignoreError() to ignore errrors with a certain code
 * @todo	implement expectError() to ignore an error with a certain code only once.
 */
class patErrorManager
{
	/**
	* method for checking whether the return value of a pat application method is a pat
	* error object.
	*
	* @static
	* @access	public
	* @param	mixed	&$object
	* @return	boolean $result	True if argument is a patError-object, false otherwise.
	*/
    function isError( &$object )
    {
		if( !is_object( $object ) )
		{
			return false;
		}



		if( strtolower(get_class( $object )) != strtolower( $GLOBALS['_pat_errorClass'] ) && !is_subclass_of( $object, $GLOBALS['_pat_errorClass'] ) )
		{
			return false;
		}

        return  true;
    }

	/**
	* wrapper for the {@link raise()} method where you do not have to specify the
	* error level - a {@link patError} object with error level E_ERROR will be returned.
	*
	* @static
	* @access	public
	* @param	string	$code	The application-internal error code for this error
	* @param	string	$msg	The error message, which may also be shown the user if need be.
	* @param	mixed	$info	Optional: Additional error information (usually only developer-relevant information that the user should never see, like a database DSN).
	* @return	object	$error	The configured patError object
	* @see		raise()
	* @see		patError
	*/
	function &raiseError( $code, $msg, $info = null )
	{
		return patErrorManager::raise( E_ERROR, $code, $msg, $info );
	}

	/**
	* wrapper for the {@link raise()} method where you do not have to specify the
	* error level - a {@link patError} object with error level E_WARNING will be returned.
	*
	* @static
	* @access	public
	* @param	string	$code	The application-internal error code for this error
	* @param	string	$msg	The error message, which may also be shown the user if need be.
	* @param	mixed	$info	Optional: Additional error information (usually only developer-relevant information that the user should never see, like a database DSN).
	* @return	object	$error	The configured patError object
	* @see		raise()
	* @see		patError
	*/
	function &raiseWarning( $code, $msg, $info = null )
	{
		return patErrorManager::raise( E_WARNING, $code, $msg, $info );
	}

	/**
	* wrapper for the {@link raise()} method where you do not have to specify the
	* error level - a {@link patError} object with error level E_NOTICE will be returned.
	*
	* @static
	* @access	public
	* @param	string	$code	The application-internal error code for this error
	* @param	string	$msg	The error message, which may also be shown the user if need be.
	* @param	mixed	$info	Optional: Additional error information (usually only developer-relevant information that the user should never see, like a database DSN).
	* @return	object	$error	The configured patError object
	* @see		raise()
	* @see		patError
	*/
	function &raiseNotice( $code, $msg, $info = null )
	{
		return patErrorManager::raise( E_NOTICE, $code, $msg, $info );
	}

	/**
	* creates a new patError object given the specified information.
	*
	* @access	public
	* @param	int		$level	The error level - use any of PHP's own error levels for this: E_ERROR, E_WARNING, E_NOTICE, E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE.
	* @param	string	$code	The application-internal error code for this error
	* @param	string	$msg	The error message, which may also be shown the user if need be.
	* @param	mixed	$info	Optional: Additional error information (usually only developer-relevant information that the user should never see, like a database DSN).
	* @return	mixed	$error	The configured patError object or false if this error should be ignored
	* @see		patError
	* @todo		implement 'simple' mode that returns just false (BC for patConfiguration)
	* @todo		either remove HTML tags and entities from output or test for enviroment!!! <b></b> in shell is ugly!
	*/
    function &raise( $level, $code, $msg, $info = null )
    {
		// ignore this error?
		if( in_array( $code, $GLOBALS['_pat_errorIgnores'] ) )
		{
			return false;
		}

		// this error was expected
		if( !empty( $GLOBALS['_pat_errorExpects'] ) )
		{
			$expected =	array_pop( $GLOBALS['_pat_errorExpects'] );
			if( in_array( $code, $expected ) )
			{
				return false;
			}
		}

		// need patError
		$class	=	$GLOBALS['_pat_errorClass'];
		if( !class_exists( $class ) )
		{
			include_once dirname( __FILE__ ) . '/'. $class .'.php';
		}

		// build error object
		$error			= new $class( $level, $code, $msg, $info );

		// see what to do with this kind of error
		$handling	=	patErrorManager::getErrorHandling( $level );

		$function	=	'handleError' . ucfirst( $handling['mode'] );
		if (is_callable( array( 'patErrorManager', $function ) )) {
			return patErrorManager::$function( $error, $handling );
		} else {
			// This is required to prevent a very unhelpful white-screen-of-death
			jexit(
				'JError::raise -> Static method JError::' . $function . ' does not exist.' .
				' Contact a developer to debug' .
				'<br /><strong>Error was</strong> ' .
				'<br />' . $error->getMessage()
			);
		}
    }

	/**
	* register a new error level
	*
	* This allows you to add custom error levels to the built-in
	* - E_NOTICE
	* - E_WARNING
	* - E_NOTICE
	*
	* You may use this level in subsequent calls to raise().
	* Error handling will be set to 'ignore' for the new level, you
	* may change it by using setErrorHandling().
	*
	* You could be using PHP's predefined constants for error levels
	* or any other integer value.
	*
	* @access	public
	* @param	integer	error level
	* @param	string	human-readable name
	* @return	boolean	true on success; false if the level already has been registered
	* @see		raise(), setErrorHandling()
	* @link		http://www.php.net/manual/en/function.error-reporting.php
	*/
	function registerErrorLevel( $level, $name )
	{
		if( isset( $GLOBALS['_pat_errorLevels'][$level] ) )
		{
			return false;
		}
		$GLOBALS['_pat_errorLevels'][$level]	=	$name;
		patErrorManager::setErrorHandling( $level, 'ignore' );
		return	true;
	}

	/**
	* sets the way the patErrorManager will handle teh different error levels. Use this
	* if you want to override the default settings.
	*
	* Error handling modes:
	* - ignore
	* - trigger
	* - verbose
	* - echo
	* - callback
	* - die
	*
	* You may also set the error handling for several modes at once using PHP's bit operations.
	* Examples:
	* - E_ALL = Set the handling for all levels
	* - E_ERROR | E_WARNING = Set the handling for errors and warnings
	* - E_ALL ^ E_ERROR = Set the handling for all levels except errors
	*
	* @static
	* @access	public
	* @param	int		$level		The error level for which to set the error handling
	* @param	string	$mode		The mode to use for the error handling.
	* @param	mixed	$options	Optional: Any options needed for the given mode.
	* @return	mixed	$result		True on success, or a patError object if failed.
	* @see		getErrorHandling()
	*/
    function setErrorHandling( $level, $mode, $options = null )
    {
		$levels	=	$GLOBALS['_pat_errorLevels'];

		$function	=	'handleError' . ucfirst( $mode );
		if( !is_callable( array( 'patErrorManager', $function ) ) )
		{
			return patErrorManager::raiseError( E_ERROR,
												'patErrorManager:' . PATERRORMANAGER_ERROR_ILLEGAL_MODE,
												'Error Handling mode is not knwon',
												'Mode: ' .  $mode . ' is not implemented.'
												);
		}

		foreach( $levels as $eLevel => $eTitle )
		{
			if( ( $level & $eLevel ) != $eLevel )
			{
				continue;
			}

			// set callback options
			if( $mode == 'callback' )
			{
				if( !is_array( $options ) )
				{
					return patErrorManager::raiseError( E_ERROR,
														'patErrorManager:' . PATERRORMANAGER_ERROR_ILLEGAL_OPTIONS,
														'Options for callback not valid'
														);
				}

				if( !is_callable( $options ) )
				{
					$tmp	=	array( 'GLOBAL' );
					if( is_array( $options ) )
					{
						$tmp[0]	=	$options[0];
						$tmp[1]	=	$options[1];
					}
					else
					{
						$tmp[1]	=	$options;
					}

					return patErrorManager::raiseError(	E_ERROR,
														'patErrorManager:' . PATERRORMANAGER_ERROR_CALLBACK_NOT_CALLABLE,
														'Function is not callable',
														'Function:' . $tmp[1]  . ' scope ' . $tmp[0] . '.'
														);
				}
			}


			// save settings
			$GLOBALS['_pat_errorHandling'][$eLevel]	=	array( 'mode' => $mode );
			if( $options	!= null )
			{
				$GLOBALS['_pat_errorHandling'][$eLevel]['options']	=	$options;
			}
		}

        return  true;
    }

	/**
	* retrieves the current error handling settings for the specified error level.
	*
	* @access	public
	* @param	int		$level		The error level to retrieve. This can be any of PHP's own error levels, e.g. E_ALL, E_NOTICE...
	* @return	array	$handling	All error handling details
	*/
    function getErrorHandling( $level )
    {
		return $GLOBALS['_pat_errorHandling'][$level];
    }

	/**
	* translate an error level
	*
	* returns the human-readable name for an error level,
	* e.g. E_ERROR will be translated to 'Error'.
	*
	* @access	public
	* @param	integer		error level
	* @return	string		human-readable representation
	*/
	function translateErrorLevel( $level )
	{
		if( isset( $GLOBALS['_pat_errorLevels'][$level] ) )
		{
			return	$GLOBALS['_pat_errorLevels'][$level];
		}
		return	'Unknown error level';
	}

	/**
	* setErrorClass
	*
	* In order to autoload this class, the filename containing that class must be
	* named like the class itself; with an appending ".php". Although the file must be stored
	* in the same directory as patErrorManager.php (this file)
	*
	* @access public
	* @param string $name classname
	* @return boolean $result true on success
	*/
    function setErrorClass( $name )
    {
		// include old error-class
		if( $name !== $GLOBALS['_pat_errorClass'] && !class_exists( $GLOBALS['_pat_errorClass'] ) )
		{
			include_once dirname( __FILE__ ) . '/' . $GLOBALS['_pat_errorClass'] . '.php';
		}

		$GLOBALS['_pat_errorClass']	=	$name;
		return true;
    }

	/**
	* add error codes to be ingored
	*
	* @static
	* @access public
	* @param mixed $codes either an array of error code or a single code that will be ignored in future
	* @return boolean $result true on success
	*/
    function addIgnore( $codes )
    {
		if( !is_array( $codes ) )
		{
			$codes	=	array( $codes );
		}

		$codes							=	array_merge( $GLOBALS['_pat_errorIgnores'], $codes );
		$GLOBALS['_pat_errorIgnores']	=	array_unique( $codes );

		return true;
    }

	/**
	* removeIgnore
	*
	*
	* @static
	* @access public
	* @return boolean $result true on success
	*/
    function removeIgnore( $codes )
    {
		if( !is_array( $codes ) )
		{
			$codes	=	array( $codes );
		}

		foreach( $codes as $code )
		{
			$index	=	array_search( $code, $GLOBALS['_pat_errorIgnores'] );
			if( $index === false )
			{
				continue;
			}

			unset( $GLOBALS['_pat_errorIgnores'][$index] );
		}

		// reorder the codes
		$GLOBALS['_pat_errorIgnores']	=	array_values( $GLOBALS['_pat_errorIgnores'] );

		return true;
    }

	/**
	* recieve all registerd error codes that will be ignored
	*
	* @static
	* @access public
	* @return array $codes list of error codes
	*/
    function getIgnore()
    {
		return $GLOBALS['_pat_errorIgnores'];
    }

	/**
	* empty list of errors to be ignored
	*
	* @static
	* @access public
	* @return boolean $result true on success
	*/
    function clearIgnore()
    {
		$GLOBALS['_pat_errorIgnores']	=	array();
		return true;
    }

	/**
	* add expected errors to stack
	*
	* @static
	* @access public
	* @param mixed $codes either an array of error code or a single code that will be ignored in future
	* @return boolean $result true on success
	*/
    function pushExpect( $codes )
    {
		if( !is_array( $codes ) )
		{
			$codes	=	array( $codes );
		}

		array_push( $GLOBALS['_pat_errorExpects'], $codes );

		return true;
    }

	/**
	* remove top of error-codes from stack
	*
	* @static
	* @access public
	* @return boolean $result true on success
	*/
    function popExpect()
    {
		if( empty( $GLOBALS['_pat_errorExpects'] ) )
		{
			return false;
		}

		array_pop( $GLOBALS['_pat_errorExpects'] );
		return true;
    }

	/**
	* recieve all registerd error codes that will be ignored
	*
	* @static
	* @access public
	* @return array $codes list of error codes
	*/
    function getExpect()
    {
		return $GLOBALS['_pat_errorExpects'];
    }

	/**
	* empty list of errors to be ignored
	*
	* @static
	* @access public
	* @return boolean $result true on success
	*/
    function clearExpect()
    {
		$GLOBALS['_pat_errorExpects']	=	array();
		return true;
    }

	/**
	* handleError: Ignore
	* Does nothing
	*
	* @access private
	* @param object $error patError-Object
	* @param array $options options for handler
	* @return object $error error-object
	* @see raise()
	*/
    function &handleErrorIgnore( &$error, $options )
    {
		return $error;
    }

	/**
	* handleError: Echo
	* display error message
	*
	* @access private
	* @param object $error patError-Object
	* @param array $options options for handler
	* @return object $error error-object
	* @see raise()
	*/
    function &handleErrorEcho( &$error, $options )
    {
		$level_human	=	patErrorManager::translateErrorLevel( $error->getLevel() );

		if( isset( $_SERVER['HTTP_HOST'] ) )
		{
			// output as html
			echo "<br /><b>pat-$level_human</b>: " . $error->getMessage() . "<br />\n";
		}
		else
		{
			// output as simple text
			if( defined( 'STDERR' ) )
			{
				fwrite( STDERR, "pat-$level_human: " . $error->getMessage() . "\n" );
			}
			else
			{
				echo "pat-$level_human: " . $error->getMessage() . "\n";
			}
		}
		return $error;
    }

	/**
	* handleError: Verbose
	* display verbose output for developing purpose
	*
	* @access private
	* @param object $error patError-Object
	* @param array $options options for handler
	* @return object $error error-object
	* @see raise()
	*/
    function &handleErrorVerbose( &$error, $options )
    {
		$level_human	=	patErrorManager::translateErrorLevel( $error->getLevel() );
		$info			=	$error->getInfo();

		if( isset( $_SERVER['HTTP_HOST'] ) )
		{
			// output as html
			echo "<br /><b>pat-$level_human</b>: " . $error->getMessage() . "<br />\n";
			if( $info != null )
			{
				echo "&nbsp;&nbsp;&nbsp;" . $error->getInfo() . "<br />\n";
			}
			echo $error->getBacktrace( true );
		}
		else
		{
			// output as simple text
			echo "pat-$level_human: " . $error->getMessage() . "\n";
			if( $info != null )
			{
				echo "    " . $error->getInfo() . "\n";
			}

		}
		return $error;
    }

	/**
	* handleError: die
	* display error-message and die
	*
	* @access private
	* @param object $error patError-Object
	* @param array $options options for handler
	* @return object $error error-object
	* @see raise()
	*/
    function &handleErrorDie( &$error, $options )
    {
		$level_human	=	patErrorManager::translateErrorLevel( $error->getLevel() );

		if( isset( $_SERVER['HTTP_HOST'] ) )
		{
			// output as html
			jexit( "<br /><b>pat-$level_human</b> " . $error->getMessage() . "<br />\n" );
		}
		else
		{
			// output as simple text
			if( defined( 'STDERR' ) )
			{
				fwrite( STDERR, "pat-$level_human " . $error->getMessage() . "\n" );
			}
			else
			{
				jexit( "pat-$level_human " . $error->getMessage() . "\n" );
			}
		}
		return $error;
    }

	/**
	* handleError: trigger
	* trigger php-error
	*
	* @access private
	* @param object $error patError-Object
	* @param array $options options for handler
	* @return object $error error-object
	* @see raise()
	*/
    function &handleErrorTrigger( &$error, $options )
    {
		switch( $error->getLevel() )
		{
			case	E_NOTICE:
				$level	=	E_USER_NOTICE;
				break;
			case	E_WARNING:
				$level	=	E_USER_WARNING;
				break;
			case	E_NOTICE:
				$level =	E_NOTICE;
				break;
			default:
				$level	=	E_USER_ERROR;
				break;
		}

		trigger_error( $error->getMessage(), $level );
		return $error;
    }

	/**
	* handleError: callback
	* forward error to custom handler
	*
	* @access private
	* @param object $error patError-Object
	* @param array $options options for handler
	* @return object $error error-object
	* @see raise()
	*/
    function &handleErrorCallback( &$error, $options )
    {
		$opt	= $options['options'];
		$result = &call_user_func( $opt, $error );
		return $result;
    }
}
?>
