Add a real exception handler

Close #1590
Close #3200
diff --git a/system/core/CodeIgniter.php b/system/core/CodeIgniter.php
index e3d3a1f..88e730b 100644
--- a/system/core/CodeIgniter.php
+++ b/system/core/CodeIgniter.php
@@ -132,7 +132,8 @@
  *  Define a custom error handler so we can log PHP errors
  * ------------------------------------------------------
  */
-	set_error_handler('_exception_handler');
+	set_error_handler('_error_handler');
+	set_exception_handler('_exception_handler');
 	register_shutdown_function('_shutdown_handler');
 
 /*
diff --git a/system/core/Common.php b/system/core/Common.php
index 8bc9d01..4277ef5 100644
--- a/system/core/Common.php
+++ b/system/core/Common.php
@@ -570,17 +570,17 @@
 
 // --------------------------------------------------------------------
 
-if ( ! function_exists('_exception_handler'))
+if ( ! function_exists('_error_handler'))
 {
 	/**
-	 * Exception Handler
+	 * Error Handler
 	 *
-	 * This is the custom exception handler that is declared at the top
-	 * of CodeIgniter.php. The main reason we use this is to permit
+	 * This is the custom error handler that is declared at the (relative)
+	 * top of CodeIgniter.php. The main reason we use this is to permit
 	 * PHP errors to be logged in our own log files since the user may
-	 * not have access to server logs. Since this function
-	 * effectively intercepts PHP errors, however, we also need
-	 * to display errors based on the current error_reporting level.
+	 * not have access to server logs. Since this function effectively
+	 * intercepts PHP errors, however, we also need to display errors
+	 * based on the current error_reporting level.
 	 * We do that with the use of a PHP error template.
 	 *
 	 * @param	int	$severity
@@ -589,7 +589,7 @@
 	 * @param	int	$line
 	 * @return	void
 	 */
-	function _exception_handler($severity, $message, $filepath, $line)
+	function _error_handler($severity, $message, $filepath, $line)
 	{
 		$is_error = (((E_ERROR | E_COMPILE_ERROR | E_CORE_ERROR | E_USER_ERROR) & $severity) === $severity);
 
@@ -632,6 +632,35 @@
 
 // ------------------------------------------------------------------------
 
+if ( ! function_exists('_exception_handler'))
+{
+	/**
+	 * Exception Handler
+	 *
+	 * Sends uncaught exceptions to the logger and displays them
+	 * only if display_errors is On so that they don't show up in
+	 * production environments.
+	 *
+	 * @param	Exception	$exception
+	 * @return	void
+	 */
+	function _exception_handler($exception)
+	{
+		$_error =& load_class('Exceptions', 'core');
+		$_error->log_exception('error', 'Exception: '.$exception->getMessage(), $exception->getFile(), $exception->getLine());
+
+		// Should we display the error?
+		if (ini_get('display_errors'))
+		{
+			$_error->show_exception($exception);
+		}
+
+		exit(1); // EXIT_ERROR
+	}
+}
+
+// ------------------------------------------------------------------------
+
 if ( ! function_exists('_shutdown_handler'))
 {
 	/**
diff --git a/system/core/Exceptions.php b/system/core/Exceptions.php
index 6324fba..0531a4e 100644
--- a/system/core/Exceptions.php
+++ b/system/core/Exceptions.php
@@ -187,6 +187,44 @@
 
 	// --------------------------------------------------------------------
 
+	public function show_exception(Exception $exception)
+	{
+		$templates_path = config_item('error_views_path');
+		if (empty($templates_path))
+		{
+			$templates_path = VIEWPATH.'errors'.DIRECTORY_SEPARATOR;
+		}
+
+		$message = $exception->getMessage();
+		if (empty($message))
+		{
+			$message = '(null)';
+		}
+
+		if (is_cli())
+		{
+			$templates_path .= 'cli'.DIRECTORY_SEPARATOR;
+		}
+		else
+		{
+			set_status_header(500);
+			$templates_path .= 'html'.DIRECTORY_SEPARATOR;
+		}
+
+		if (ob_get_level() > $this->ob_level + 1)
+		{
+			ob_end_flush();
+		}
+
+		ob_start();
+		include($templates_path.'error_exception.php');
+		$buffer = ob_get_contents();
+		ob_end_clean();
+		echo $buffer;
+	}
+
+	// --------------------------------------------------------------------
+
 	/**
 	 * Native PHP error handler
 	 *