diff --git a/inc/ErrorHandler.php b/inc/ErrorHandler.php new file mode 100644 index 000000000..184caaebb --- /dev/null +++ b/inc/ErrorHandler.php @@ -0,0 +1,145 @@ +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 << + +$title + +
+

$title

+

$msg

+

$logged

+
+ + +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) . '
' . hsc(get_class($e) . ': ' . $e->getMessage()); + if (self::logException($e)) $msg .= '
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; + } +} diff --git a/inc/Exception/FatalException.php b/inc/Exception/FatalException.php new file mode 100644 index 000000000..f52cc6647 --- /dev/null +++ b/inc/Exception/FatalException.php @@ -0,0 +1,11 @@ +isSingleton()) { - return class_exists($class, true) ? new $class : null; + try { + //plugin already loaded? + if (!empty($DOKU_PLUGINS[$type][$name])) { + 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 - $inf = confToHash(DOKU_PLUGIN . "$plugin/plugin.info.txt"); - if ($inf['base'] && $inf['base'] != $plugin) { - msg( - sprintf( - "Plugin installed incorrectly. Rename plugin directory '%s' to '%s'.", - hsc($plugin), - hsc( - $inf['base'] - ) - ), -1 - ); - } elseif (preg_match('/^' . DOKU_PLUGIN_NAME_REGEX . '$/', $plugin) !== 1) { - msg( - sprintf( - "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 - ); + //construct class and instantiate + if (!class_exists($class, true)) { + # the plugin might be in the wrong directory + $inf = confToHash(DOKU_PLUGIN . "$plugin/plugin.info.txt"); + if ($inf['base'] && $inf['base'] != $plugin) { + msg( + sprintf( + "Plugin installed incorrectly. Rename plugin directory '%s' to '%s'.", + hsc($plugin), + hsc( + $inf['base'] + ) + ), -1 + ); + } elseif (preg_match('/^' . DOKU_PLUGIN_NAME_REGEX . '$/', $plugin) !== 1) { + msg( + sprintf( + "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; } - $DOKU_PLUGINS[$type][$name] = new $class; return $DOKU_PLUGINS[$type][$name]; } diff --git a/inc/init.php b/inc/init.php index f9bb53472..f4ca7e8d4 100644 --- a/inc/init.php +++ b/inc/init.php @@ -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.'inc/load.php'); +// from now on everything is an exception +\dokuwiki\ErrorHandler::register(); + // disable gzip if not available define('DOKU_HAS_BZIP', function_exists('bzopen')); define('DOKU_HAS_GZIP', function_exists('gzopen')); diff --git a/inc/load.php b/inc/load.php index 46cd91f4c..68d425584 100644 --- a/inc/load.php +++ b/inc/load.php @@ -108,7 +108,11 @@ function load_autoload($name){ $name = str_replace('/test/', '/_test/', $name); // no underscore in test namespace $file = DOKU_PLUGIN . substr($name, 16) . '.php'; if(file_exists($file)) { - require $file; + try { + require $file; + } catch (\Throwable $e) { + \dokuwiki\ErrorHandler::showExceptionMsg($e, "Error loading plugin $name"); + } return true; } } @@ -118,7 +122,11 @@ function load_autoload($name){ $name = str_replace('/test/', '/_test/', $name); // no underscore in test namespace $file = DOKU_INC.'lib/tpl/' . substr($name, 18) . '.php'; if(file_exists($file)) { - require $file; + try { + require $file; + } catch (\Throwable $e) { + \dokuwiki\ErrorHandler::showExceptionMsg($e, "Error loading template $name"); + } return true; } } @@ -144,7 +152,11 @@ function load_autoload($name){ $c = ((count($m) === 4) ? "/{$m[3]}" : ''); $plg = DOKU_PLUGIN . "{$m[2]}/{$m[1]}$c.php"; if(file_exists($plg)){ - require $plg; + try { + require $plg; + } catch (\Throwable $e) { + \dokuwiki\ErrorHandler::showExceptionMsg($e, "Error loading plugin {$m[2]}"); + } } return true; }