/**
* Render the error page based on an exception.
*
* @param \Throwable $error An Exception or Throwable (PHP 7+) object for which to render the error page.
*
* @return void
*
* @since 3.0
*/
public static function render(\Throwable $error)
{
try {
$app = Factory::getApplication();
// Flag if we are on cli
$isCli = $app->isClient('cli');
// If site is offline and it's a 404 error, just go to index (to see offline message, instead of 404)
if (!$isCli && $error->getCode() == '404' && $app->get('offline') == 1) {
$app->redirect('index.php');
}
/*
* Try and determine the format to render the error page in
*
* First we check if a Document instance was registered to Factory and use the type from that if available
* If a type doesn't exist for that format, we try to use the format from the application's Input object
* Lastly, if all else fails, we default onto the HTML format to at least render something
*/
if (Factory::$document) {
$format = Factory::$document->getType();
} else {
$format = $app->input->getString('format', 'html');
}
try {
$renderer = AbstractRenderer::getRenderer($format);
} catch (\InvalidArgumentException $e) {
// Default to the HTML renderer
$renderer = AbstractRenderer::getRenderer('html');
}
// Reset the document object in the factory, this gives us a clean slate and lets everything render properly
Factory::$document = $renderer->getDocument();
Factory::getApplication()->loadDocument(Factory::$document);
$data = $renderer->render($error);
// If nothing was rendered, just use the message from the Exception
if (empty($data)) {
$data = $error->getMessage();
}
if ($isCli) {
echo $data;
} else {
/** @var CMSApplication $app */
// Do not allow cache
$app->allowCache(false);
$app->setBody($data);
}
// This return is needed to ensure the test suite does not trigger the non-Exception handling below
return;
} catch (\Throwable $errorRendererError) {
// Pass the error down
}
/*
* To reach this point in the code means there was an error creating the error page.
*
* Let global handler to handle the error, @see bootstrap.php
*/
if (isset($errorRendererError)) {
/*
* Here the thing, at this point we have 2 exceptions:
* $errorRendererError - the error caused by error renderer
* $error - the main error
*
* We need to show both exceptions, without loss of trace information, so use a bit of magic to merge them.
*
* Use exception nesting feature: rethrow the exceptions, an exception thrown in a finally block
* will take unhandled exception as previous.
* So PHP will add $error Exception as previous to $errorRendererError Exception to keep full error stack.
*/
try {
try {
throw $error;
} finally {
throw $errorRendererError;
}
} catch (\Throwable $finalError) {
throw $finalError;
}
} else {
throw $error;
}
}