diff --git a/libraries/cms.php b/libraries/cms.php index 6756555fbf17e..2f7ed4db58282 100644 --- a/libraries/cms.php +++ b/libraries/cms.php @@ -68,7 +68,7 @@ } // Register a handler for uncaught exceptions that shows a pretty error page when possible -set_exception_handler(array('JErrorPage', 'render')); +set_exception_handler(array('Joomla\CMS\Exception\ExceptionHandler', 'handleException')); // Set up the message queue logger for web requests if (array_key_exists('REQUEST_METHOD', $_SERVER)) diff --git a/libraries/src/Exception/ExceptionHandler.php b/libraries/src/Exception/ExceptionHandler.php index 2f715129d0222..c6e4754c36420 100644 --- a/libraries/src/Exception/ExceptionHandler.php +++ b/libraries/src/Exception/ExceptionHandler.php @@ -10,6 +10,11 @@ defined('JPATH_PLATFORM') or die; +use Joomla\CMS\Document\Document; +use Joomla\CMS\Factory; +use Joomla\CMS\Language\Text; +use Joomla\CMS\Log\Log; + /** * Displays the custom error page when an uncaught exception occurs. * @@ -17,6 +22,25 @@ */ class ExceptionHandler { + /** + * Handles exceptions: logs errors and renders error page. + * + * @param \Exception|\Throwable $error An Exception or Throwable (PHP 7+) object for which to render the error page. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public static function handleException($error) + { + if (static::isException($error)) + { + static::logException($error); + } + + static::render($error); + } + /** * Render the error page based on an exception. * @@ -28,38 +52,12 @@ class ExceptionHandler */ public static function render($error) { - $expectedClass = PHP_MAJOR_VERSION >= 7 ? '\Throwable' : '\Exception'; - $isException = $error instanceof $expectedClass; - - // In PHP 5, the $error object should be an instance of \Exception; PHP 7 should be a Throwable implementation - if ($isException) + // Render template error page for exceptions only, because template will expect exception object + if (static::isException($error)) { try { - // Try to log the error, but don't let the logging cause a fatal error - try - { - \JLog::add( - sprintf( - 'Uncaught %1$s of type %2$s thrown. Stack trace: %3$s', - $expectedClass, - get_class($error), - $error->getTraceAsString() - ), - \JLog::CRITICAL, - 'error' - ); - } - catch (\Throwable $e) - { - // Logging failed, don't make a stink about it though - } - catch (\Exception $e) - { - // Logging failed, don't make a stink about it though - } - - $app = \JFactory::getApplication(); + $app = Factory::getApplication(); // If site is offline and it's a 404 error, just go to index (to see offline message, instead of 404) if ($error->getCode() == '404' && $app->get('offline') == 1) @@ -75,14 +73,14 @@ public static function render($error) 'direction' => 'ltr', ); - // If there is a \JLanguage instance in \JFactory then let's pull the language and direction from its metadata - if (\JFactory::$language) + // If there is a \JLanguage instance in Factory then let's pull the language and direction from its metadata + if (Factory::$language) { - $attributes['language'] = \JFactory::getLanguage()->getTag(); - $attributes['direction'] = \JFactory::getLanguage()->isRtl() ? 'rtl' : 'ltr'; + $attributes['language'] = Factory::getLanguage()->getTag(); + $attributes['direction'] = Factory::getLanguage()->isRtl() ? 'rtl' : 'ltr'; } - $document = \JDocument::getInstance('error', $attributes); + $document = Document::getInstance('error', $attributes); if (!$document) { @@ -96,12 +94,10 @@ public static function render($error) // Push the error object into the document $document->setError($error); - if (ob_get_contents()) - { - ob_end_clean(); - } + // Clear buffered output at all levels in non-test mode + $callerFunction = static::getCallerFunctionName(); - $document->setTitle(\JText::_('ERROR') . ': ' . $error->getCode()); + $document->setTitle(Text::_('ERROR') . ': ' . $error->getCode()); $data = $document->render( false, @@ -148,7 +144,7 @@ public static function render($error) $message = 'Error'; - if ($isException) + if (static::isException($error)) { // Make sure we do not display sensitive data in production environments if (ini_get('display_errors')) @@ -168,4 +164,70 @@ public static function render($error) jexit(1); } + + /** + * Returns name of function, that called current routine or false on failure. Current routine is + * routine, calling {@see getCallerMethod}. + * + * @return string|false + * + * @since __DEPLOY_VERSION__ + */ + protected static function getCallerFunctionName() + { + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + + return isset($backtrace[2]['function']) ? $backtrace[2]['function'] : false; + } + + /** + * Checks if given error belong to PHP exception class (\Throwable for PHP 7+, \Exception for PHP 5-). + * + * @param mixed $error Any error value. + * + * @return bool + * + * @since __DEPLOY_VERSION__ + */ + protected static function isException($error) + { + $expectedClass = PHP_MAJOR_VERSION >= 7 ? '\Throwable' : '\Exception'; + + return $error instanceof $expectedClass; + } + + /** + * Logs exception, catching all possible errors during logging. + * + * @param \Exception|\Throwable $error An Exception or Throwable (PHP 7+) object to get error message from. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + protected static function logException($error) + { + // Try to log the error, but don't let the logging cause a fatal error + try + { + Log::add( + sprintf( + 'Uncaught %1$s of type %2$s thrown. Stack trace: %3$s', + PHP_MAJOR_VERSION >= 7 ? 'Throwable' : 'Exception', + get_class($error), + $error->getTraceAsString() + ), + Log::CRITICAL, + 'error' + ); + } + catch (\Throwable $e) + { + // Logging failed, don't make a stink about it though + } + catch (\Exception $e) + { + // Logging failed, don't make a stink about it though + } + } }