* @version $Revision: $  $Date: $
 * @package System.Exceptions
 * @since 3.0
 */
class TErrorHandler extends TComponent implements IModule
{
	/**
	 * error template file basename
	 */
	const ERROR_FILE_NAME='error';
	/**
	 * exception template file basename
	 */
	const EXCEPTION_FILE_NAME='exception';
	/**
	 * number of lines before and after the error line to be displayed in case of an exception
	 */
	const SOURCE_LINES=12;
	/**
	 * @var string module ID
	 */
	private $_id='error';
	/**
	 * @var TApplication application instance
	 */
	private $_application;
	/**
	 * @var string error template directory
	 */
	private $_templatePath=null;
	/**
	 * Initializes the module.
	 * This method is required by IModule and is invoked by application.
	 * @param TApplication application
	 * @param TXmlElement module configuration
	 */
	public function init($application,$config)
	{
		$this->_application=$application;
		$application->setErrorHandler($this);
	}
	/**
	 * @return string id of this module
	 */
	public function getID()
	{
		return $this->_id;
	}
	/**
	 * @param string id of this module
	 */
	public function setID($value)
	{
		$this->_id=$value;
	}
	/**
	 * @return string the directory containing error template files.
	 */
	public function getErrorTemplatePath()
	{
		return $this->_templatePath;
	}
	/**
	 * Sets the path storing all error and exception template files.
	 * The path must be in namespace format, such as System.Exceptions (which is the default).
	 * @param string template path in namespace format
	 * @throws TConfigurationException if the template path is invalid
	 */
	public function setErrorTemplatePath($value)
	{
		if(($templatePath=Prado::getPathOfNamespace($this->_templatePath))!==null && is_dir($templatePath))
			$this->_templatePath=$templatePath;
		else
			throw new TConfigurationException('errorhandler_errortemplatepath_invalid',$value);
	}
	/**
	 * Handles PHP user errors and exceptions.
	 * This is the event handler responding to the Error event
	 * raised in {@link TApplication}.
	 * The method mainly uses appropriate template to display the error/exception.
	 * It terminates the application immediately after the error is displayed.
	 * @param mixed sender of the event
	 * @param mixed event parameter (if the event is raised by TApplication, it refers to the exception instance)
	 */
	public function handleError($sender,$param)
	{
		static $handling=false;
		// We need to restore error and exception handlers,
		// because within error and exception handlers, new errors and exceptions
		// cannot be handled properly by PHP
		restore_error_handler();
		restore_exception_handler();
		// ensure that we do not enter infinite loop of error handling
		if($handling)
			$this->handleRecursiveError($param);
		else
		{
			$handling=true;
			if(($response=Prado::getApplication()->getResponse())!==null)
				$response->clear();
			header('Content-Type: text/html; charset=UTF-8');
			if($param instanceof THttpException)
				$this->handleExternalError($param->getStatusCode(),$param);
			else if(Prado::getApplication()->getMode()==='Debug')
				$this->displayException($param);
			else
				$this->handleExternalError(500,$param);
		}
		exit(1);
	}
	/**
	 * Displays error to the client user.
	 * THttpException and errors happened when the application is in Debug
	 * mode will be displayed to the client user.
	 * @param integer response status code
	 * @param Exception exception instance
	 */
	protected function handleExternalError($statusCode,$exception)
	{
		if(!($exception instanceof THttpException))
			error_log($exception->__toString());
		if($this->_templatePath===null)
			$this->_templatePath=Prado::getFrameworkPath().'/Exceptions/templates';
		$base=$this->_templatePath.'/'.self::ERROR_FILE_NAME;
		$lang=Prado::getPreferredLanguage();
		if(is_file("$base$statusCode-$lang.html"))
			$errorFile="$base$statusCode-$lang.html";
		else if(is_file("$base$statusCode.html"))
			$errorFile="$base$statusCode.html";
		else if(is_file("$base-$lang.html"))
			$errorFile="$base-$lang.html";
		else
			$errorFile="$base.html";
		if(($content=@file_get_contents($errorFile))===false)
			die("Unable to open error template file '$errorFile'.");
		$serverAdmin=isset($_SERVER['SERVER_ADMIN'])?$_SERVER['SERVER_ADMIN']:'';
		$tokens=array(
			'%%StatusCode%%' => "$statusCode",
			'%%ErrorMessage%%' => htmlspecialchars($exception->getMessage()),
			'%%ServerAdmin%%' => $serverAdmin,
			'%%Version%%' => $_SERVER['SERVER_SOFTWARE'].' PRADO/'.Prado::getVersion(),
			'%%Time%%' => strftime('%Y-%m-%d %H:%m',time())
		);
		echo strtr($content,$tokens);
	}
	/**
	 * Handles error occurs during error handling (called recursive error).
	 * THttpException and errors happened when the application is in Debug
	 * mode will be displayed to the client user.
	 * Error is displayed without using existing template to prevent further errors.
	 * @param Exception exception instance
	 */
	protected function handleRecursiveError($exception)
	{
		if(Prado::getApplication()->getMode()==='Debug')
		{
			echo "Recursive Error\n";
			echo "Recursive Error
\n";
			echo "".$exception->__toString()."
\n";
			echo "";
		}
		else
		{
			error_log("Error happened while processing an existing error:\n".$param->__toString());
			header('HTTP/1.0 500 Internal Error');
		}
	}
	/**
	 * Displays exception information.
	 * Exceptions are displayed with rich context information, including
	 * the call stack and the context source code.
	 * This method is only invoked when application is in Debug mode.
	 * @param Exception exception instance
	 */
	protected function displayException($exception)
	{
		$lines=file($exception->getFile());
		$errorLine=$exception->getLine();
		$beginLine=$errorLine-self::SOURCE_LINES>=0?$errorLine-self::SOURCE_LINES:0;
		$endLine=$errorLine+self::SOURCE_LINES<=count($lines)?$errorLine+self::SOURCE_LINES:count($lines);
		$source='';
		for($i=$beginLine-1;$i<$endLine;++$i)
		{
			if($i===$errorLine-1)
			{
				$line=htmlspecialchars(sprintf("%04d: %s",$i+1,str_replace("\t",'    ',$lines[$i])));
				$source.="".$line."
";
			}
			else
				$source.=htmlspecialchars(sprintf("%04d: %s",$i+1,str_replace("\t",'    ',$lines[$i])));
		}
		$tokens=array(
			'%%ErrorType%%' => get_class($exception),
			'%%ErrorMessage%%' => htmlspecialchars($exception->getMessage()),
			'%%SourceFile%%' => htmlspecialchars($exception->getFile()).' ('.$exception->getLine().')',
			'%%SourceCode%%' => $source,
			'%%StackTrace%%' => htmlspecialchars($exception->getTraceAsString()),
			'%%Version%%' => $_SERVER['SERVER_SOFTWARE'].' PRADO/'.Prado::getVersion(),
			'%%Time%%' => strftime('%Y-%m-%d %H:%m',time())
		);
		$lang=Prado::getPreferredLanguage();
		$exceptionFile=Prado::getFrameworkPath().'/Exceptions/templates/'.self::EXCEPTION_FILE_NAME.'-'.$lang.'.html';
		if(!is_file($exceptionFile))
			$exceptionFile=Prado::getFrameworkPath().'/Exceptions/templates/'.self::EXCEPTION_FILE_NAME.'.html';
		if(($content=@file_get_contents($exceptionFile))===false)
			die("Unable to open exception template file '$exceptionFile'.");
		echo strtr($content,$tokens);
	}
}
?>