Merge branch 'globalErrorHandling'
* globalErrorHandling: fixed plugin name output on load error no need to convert Errors to Exceptions FatalException and proper plugin detection add shutdown handler to even manage fatal errors reflow overlong line better exception handling on plugin loading log stacktrace to error log guess which plugin was the source of an exception introduce a global error handler
This commit is contained in:
commit
b102b83e6d
|
@ -0,0 +1,145 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace dokuwiki;
|
||||||
|
|
||||||
|
use dokuwiki\Exception\FatalException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manage the global handling of errors and exceptions
|
||||||
|
*
|
||||||
|
* Developer may use this to log and display exceptions themselves
|
||||||
|
*/
|
||||||
|
class ErrorHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Register the default error handling
|
||||||
|
*/
|
||||||
|
public static function register()
|
||||||
|
{
|
||||||
|
if (!defined('DOKU_UNITTEST')) {
|
||||||
|
set_exception_handler([ErrorHandler::class, 'fatalException']);
|
||||||
|
register_shutdown_function([ErrorHandler::class, 'fatalShutdown']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default Exception handler to show a nice user message before dieing
|
||||||
|
*
|
||||||
|
* The exception is logged to the error log
|
||||||
|
*
|
||||||
|
* @param \Throwable $e
|
||||||
|
*/
|
||||||
|
public static function fatalException($e)
|
||||||
|
{
|
||||||
|
$plugin = self::guessPlugin($e);
|
||||||
|
$title = hsc(get_class($e) . ': ' . $e->getMessage());
|
||||||
|
$msg = 'An unforeseen error has occured. This is most likely a bug somewhere.';
|
||||||
|
if ($plugin) $msg .= ' It might be a problem in the ' . $plugin . ' plugin.';
|
||||||
|
$logged = self::logException($e)
|
||||||
|
? 'More info has been written to the DokuWiki _error.log'
|
||||||
|
: $e->getFile() . ':' . $e->getLine();
|
||||||
|
|
||||||
|
echo <<<EOT
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head><title>$title</title></head>
|
||||||
|
<body style="font-family: Arial, sans-serif">
|
||||||
|
<div style="width:60%; margin: auto; background-color: #fcc;
|
||||||
|
border: 1px solid #faa; padding: 0.5em 1em;">
|
||||||
|
<h1 style="font-size: 120%">$title</h1>
|
||||||
|
<p>$msg</p>
|
||||||
|
<p>$logged</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
EOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method to display an error message for the given Exception
|
||||||
|
*
|
||||||
|
* @param \Throwable $e
|
||||||
|
* @param string $intro
|
||||||
|
*/
|
||||||
|
public static function showExceptionMsg($e, $intro = 'Error!')
|
||||||
|
{
|
||||||
|
$msg = hsc($intro) . '<br />' . hsc(get_class($e) . ': ' . $e->getMessage());
|
||||||
|
if (self::logException($e)) $msg .= '<br />More info is available in the _error.log';
|
||||||
|
msg($msg, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last resort to handle fatal errors that still can't be caught
|
||||||
|
*/
|
||||||
|
public static function fatalShutdown()
|
||||||
|
{
|
||||||
|
$error = error_get_last();
|
||||||
|
// Check if it's a core/fatal error, otherwise it's a normal shutdown
|
||||||
|
if (
|
||||||
|
$error !== null &&
|
||||||
|
in_array(
|
||||||
|
$error['type'],
|
||||||
|
[
|
||||||
|
E_ERROR,
|
||||||
|
E_CORE_ERROR,
|
||||||
|
E_COMPILE_ERROR,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
self::fatalException(
|
||||||
|
new FatalException($error['message'], 0, $error['type'], $error['file'], $error['line'])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log the given exception to the error log
|
||||||
|
*
|
||||||
|
* @param \Throwable $e
|
||||||
|
* @return bool false if the logging failed
|
||||||
|
*/
|
||||||
|
public static function logException($e)
|
||||||
|
{
|
||||||
|
global $conf;
|
||||||
|
|
||||||
|
$log = join("\t", [
|
||||||
|
gmdate('c'),
|
||||||
|
get_class($e),
|
||||||
|
$e->getFile() . '(' . $e->getLine() . ')',
|
||||||
|
$e->getMessage(),
|
||||||
|
]) . "\n";
|
||||||
|
$log .= $e->getTraceAsString() . "\n";
|
||||||
|
return io_saveFile($conf['cachedir'] . '/_error.log', $log, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the the stacktrace for plugin files
|
||||||
|
*
|
||||||
|
* @param \Throwable $e
|
||||||
|
* @return false|string
|
||||||
|
*/
|
||||||
|
protected static function guessPlugin($e)
|
||||||
|
{
|
||||||
|
if (preg_match('/lib\/plugins\/(\w+)\//', str_replace('\\', '/', $e->getFile()), $match)) {
|
||||||
|
return $match[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($e->getTrace() as $line) {
|
||||||
|
if (
|
||||||
|
isset($line['class']) &&
|
||||||
|
preg_match('/\w+?_plugin_(\w+)/', $line['class'], $match)
|
||||||
|
) {
|
||||||
|
return $match[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
isset($line['file']) &&
|
||||||
|
preg_match('/lib\/plugins\/(\w+)\//', str_replace('\\', '/', $line['file']), $match)
|
||||||
|
) {
|
||||||
|
return $match[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace dokuwiki\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fatal Errors are converted into this Exception in out Shutdown handler
|
||||||
|
*/
|
||||||
|
class FatalException extends \ErrorException
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
namespace dokuwiki\Extension;
|
namespace dokuwiki\Extension;
|
||||||
|
|
||||||
|
use dokuwiki\ErrorHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to encapsulate access to dokuwiki plugins
|
* Class to encapsulate access to dokuwiki plugins
|
||||||
*
|
*
|
||||||
|
@ -90,42 +92,49 @@ class PluginController
|
||||||
|
|
||||||
$class = $type . '_plugin_' . $name;
|
$class = $type . '_plugin_' . $name;
|
||||||
|
|
||||||
//plugin already loaded?
|
try {
|
||||||
if (!empty($DOKU_PLUGINS[$type][$name])) {
|
//plugin already loaded?
|
||||||
if ($new || !$DOKU_PLUGINS[$type][$name]->isSingleton()) {
|
if (!empty($DOKU_PLUGINS[$type][$name])) {
|
||||||
return class_exists($class, true) ? new $class : null;
|
if ($new || !$DOKU_PLUGINS[$type][$name]->isSingleton()) {
|
||||||
|
|
||||||
|
return class_exists($class, true) ? new $class : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $DOKU_PLUGINS[$type][$name];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $DOKU_PLUGINS[$type][$name];
|
//construct class and instantiate
|
||||||
}
|
if (!class_exists($class, true)) {
|
||||||
|
# the plugin might be in the wrong directory
|
||||||
//construct class and instantiate
|
$inf = confToHash(DOKU_PLUGIN . "$plugin/plugin.info.txt");
|
||||||
if (!class_exists($class, true)) {
|
if ($inf['base'] && $inf['base'] != $plugin) {
|
||||||
|
msg(
|
||||||
# the plugin might be in the wrong directory
|
sprintf(
|
||||||
$inf = confToHash(DOKU_PLUGIN . "$plugin/plugin.info.txt");
|
"Plugin installed incorrectly. Rename plugin directory '%s' to '%s'.",
|
||||||
if ($inf['base'] && $inf['base'] != $plugin) {
|
hsc($plugin),
|
||||||
msg(
|
hsc(
|
||||||
sprintf(
|
$inf['base']
|
||||||
"Plugin installed incorrectly. Rename plugin directory '%s' to '%s'.",
|
)
|
||||||
hsc($plugin),
|
), -1
|
||||||
hsc(
|
);
|
||||||
$inf['base']
|
} elseif (preg_match('/^' . DOKU_PLUGIN_NAME_REGEX . '$/', $plugin) !== 1) {
|
||||||
)
|
msg(
|
||||||
), -1
|
sprintf(
|
||||||
);
|
"Plugin name '%s' is not a valid plugin name, only the characters a-z ".
|
||||||
} elseif (preg_match('/^' . DOKU_PLUGIN_NAME_REGEX . '$/', $plugin) !== 1) {
|
"and 0-9 are allowed. " .
|
||||||
msg(
|
'Maybe the plugin has been installed in the wrong directory?', hsc($plugin)
|
||||||
sprintf(
|
), -1
|
||||||
"Plugin name '%s' is not a valid plugin name, only the characters a-z and 0-9 are allowed. " .
|
);
|
||||||
'Maybe the plugin has been installed in the wrong directory?', hsc($plugin)
|
}
|
||||||
), -1
|
return null;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
$DOKU_PLUGINS[$type][$name] = new $class;
|
||||||
|
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
ErrorHandler::showExceptionMsg($e, sprintf('Failed to load plugin %s', $plugin));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$DOKU_PLUGINS[$type][$name] = new $class;
|
|
||||||
return $DOKU_PLUGINS[$type][$name];
|
return $DOKU_PLUGINS[$type][$name];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -199,6 +199,9 @@ if (empty($plugin_controller_class)) $plugin_controller_class = dokuwiki\Extensi
|
||||||
require_once(DOKU_INC.'vendor/autoload.php');
|
require_once(DOKU_INC.'vendor/autoload.php');
|
||||||
require_once(DOKU_INC.'inc/load.php');
|
require_once(DOKU_INC.'inc/load.php');
|
||||||
|
|
||||||
|
// from now on everything is an exception
|
||||||
|
\dokuwiki\ErrorHandler::register();
|
||||||
|
|
||||||
// disable gzip if not available
|
// disable gzip if not available
|
||||||
define('DOKU_HAS_BZIP', function_exists('bzopen'));
|
define('DOKU_HAS_BZIP', function_exists('bzopen'));
|
||||||
define('DOKU_HAS_GZIP', function_exists('gzopen'));
|
define('DOKU_HAS_GZIP', function_exists('gzopen'));
|
||||||
|
|
18
inc/load.php
18
inc/load.php
|
@ -108,7 +108,11 @@ function load_autoload($name){
|
||||||
$name = str_replace('/test/', '/_test/', $name); // no underscore in test namespace
|
$name = str_replace('/test/', '/_test/', $name); // no underscore in test namespace
|
||||||
$file = DOKU_PLUGIN . substr($name, 16) . '.php';
|
$file = DOKU_PLUGIN . substr($name, 16) . '.php';
|
||||||
if(file_exists($file)) {
|
if(file_exists($file)) {
|
||||||
require $file;
|
try {
|
||||||
|
require $file;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
\dokuwiki\ErrorHandler::showExceptionMsg($e, "Error loading plugin $name");
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,7 +122,11 @@ function load_autoload($name){
|
||||||
$name = str_replace('/test/', '/_test/', $name); // no underscore in test namespace
|
$name = str_replace('/test/', '/_test/', $name); // no underscore in test namespace
|
||||||
$file = DOKU_INC.'lib/tpl/' . substr($name, 18) . '.php';
|
$file = DOKU_INC.'lib/tpl/' . substr($name, 18) . '.php';
|
||||||
if(file_exists($file)) {
|
if(file_exists($file)) {
|
||||||
require $file;
|
try {
|
||||||
|
require $file;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
\dokuwiki\ErrorHandler::showExceptionMsg($e, "Error loading template $name");
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,7 +152,11 @@ function load_autoload($name){
|
||||||
$c = ((count($m) === 4) ? "/{$m[3]}" : '');
|
$c = ((count($m) === 4) ? "/{$m[3]}" : '');
|
||||||
$plg = DOKU_PLUGIN . "{$m[2]}/{$m[1]}$c.php";
|
$plg = DOKU_PLUGIN . "{$m[2]}/{$m[1]}$c.php";
|
||||||
if(file_exists($plg)){
|
if(file_exists($plg)){
|
||||||
require $plg;
|
try {
|
||||||
|
require $plg;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
\dokuwiki\ErrorHandler::showExceptionMsg($e, "Error loading plugin {$m[2]}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue