ErrorHandler.php 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. <?php
  2. namespace dokuwiki;
  3. use dokuwiki\Exception\FatalException;
  4. /**
  5. * Manage the global handling of errors and exceptions
  6. *
  7. * Developer may use this to log and display exceptions themselves
  8. */
  9. class ErrorHandler
  10. {
  11. /**
  12. * Standard error codes used in PHP errors
  13. * @see https://www.php.net/manual/en/errorfunc.constants.php
  14. */
  15. const ERRORCODES = [
  16. 1 => 'E_ERROR',
  17. 2 => 'E_WARNING',
  18. 4 => 'E_PARSE',
  19. 8 => 'E_NOTICE',
  20. 16 => 'E_CORE_ERROR',
  21. 32 => 'E_CORE_WARNING',
  22. 64 => 'E_COMPILE_ERROR',
  23. 128 => 'E_COMPILE_WARNING',
  24. 256 => 'E_USER_ERROR',
  25. 512 => 'E_USER_WARNING',
  26. 1024 => 'E_USER_NOTICE',
  27. 2048 => 'E_STRICT',
  28. 4096 => 'E_RECOVERABLE_ERROR',
  29. 8192 => 'E_DEPRECATED',
  30. 16384 => 'E_USER_DEPRECATED',
  31. ];
  32. /**
  33. * Register the default error handling
  34. */
  35. public static function register()
  36. {
  37. if (!defined('DOKU_UNITTEST')) {
  38. set_exception_handler([ErrorHandler::class, 'fatalException']);
  39. register_shutdown_function([ErrorHandler::class, 'fatalShutdown']);
  40. set_error_handler(
  41. [ErrorHandler::class, 'errorHandler'],
  42. E_WARNING|E_USER_ERROR|E_USER_WARNING|E_RECOVERABLE_ERROR
  43. );
  44. }
  45. }
  46. /**
  47. * Default Exception handler to show a nice user message before dieing
  48. *
  49. * The exception is logged to the error log
  50. *
  51. * @param \Throwable $e
  52. */
  53. public static function fatalException($e)
  54. {
  55. $plugin = self::guessPlugin($e);
  56. $title = hsc(get_class($e) . ': ' . $e->getMessage());
  57. $msg = 'An unforeseen error has occured. This is most likely a bug somewhere.';
  58. if ($plugin) $msg .= ' It might be a problem in the ' . $plugin . ' plugin.';
  59. $logged = self::logException($e)
  60. ? 'More info has been written to the DokuWiki error log.'
  61. : $e->getFile() . ':' . $e->getLine();
  62. echo <<<EOT
  63. <!DOCTYPE html>
  64. <html>
  65. <head><title>$title</title></head>
  66. <body style="font-family: Arial, sans-serif">
  67. <div style="width:60%; margin: auto; background-color: #fcc;
  68. border: 1px solid #faa; padding: 0.5em 1em;">
  69. <h1 style="font-size: 120%">$title</h1>
  70. <p>$msg</p>
  71. <p>$logged</p>
  72. </div>
  73. </body>
  74. </html>
  75. EOT;
  76. }
  77. /**
  78. * Convenience method to display an error message for the given Exception
  79. *
  80. * @param \Throwable $e
  81. * @param string $intro
  82. */
  83. public static function showExceptionMsg($e, $intro = 'Error!')
  84. {
  85. $msg = hsc($intro) . '<br />' . hsc(get_class($e) . ': ' . $e->getMessage());
  86. if (self::logException($e)) $msg .= '<br />More info is available in the error log.';
  87. msg($msg, -1);
  88. }
  89. /**
  90. * Last resort to handle fatal errors that still can't be caught
  91. */
  92. public static function fatalShutdown()
  93. {
  94. $error = error_get_last();
  95. // Check if it's a core/fatal error, otherwise it's a normal shutdown
  96. if (
  97. $error !== null &&
  98. in_array(
  99. $error['type'],
  100. [
  101. E_ERROR,
  102. E_CORE_ERROR,
  103. E_COMPILE_ERROR,
  104. ]
  105. )
  106. ) {
  107. self::fatalException(
  108. new FatalException($error['message'], 0, $error['type'], $error['file'], $error['line'])
  109. );
  110. }
  111. }
  112. /**
  113. * Log the given exception to the error log
  114. *
  115. * @param \Throwable $e
  116. * @return bool false if the logging failed
  117. */
  118. public static function logException($e)
  119. {
  120. if (is_a($e, \ErrorException::class)) {
  121. $prefix = self::ERRORCODES[$e->getSeverity()];
  122. } else {
  123. $prefix = get_class($e);
  124. }
  125. return Logger::getInstance()->log(
  126. $prefix . ': ' . $e->getMessage(),
  127. $e->getTraceAsString(),
  128. $e->getFile(),
  129. $e->getLine()
  130. );
  131. }
  132. /**
  133. * Error handler to log non-exception errors
  134. *
  135. * @param int $errno
  136. * @param string $errstr
  137. * @param string $errfile
  138. * @param int $errline
  139. * @return bool
  140. */
  141. public static function errorHandler($errno, $errstr, $errfile, $errline)
  142. {
  143. global $conf;
  144. // ignore supressed warnings
  145. if (!(error_reporting() & $errno)) return false;
  146. $ex = new \ErrorException(
  147. $errstr,
  148. 0,
  149. $errno,
  150. $errfile,
  151. $errline
  152. );
  153. self::logException($ex);
  154. if($ex->getSeverity() === E_WARNING && $conf['hidewarnings']) {
  155. return true;
  156. }
  157. return false;
  158. }
  159. /**
  160. * Checks the the stacktrace for plugin files
  161. *
  162. * @param \Throwable $e
  163. * @return false|string
  164. */
  165. protected static function guessPlugin($e)
  166. {
  167. if (preg_match('/lib\/plugins\/(\w+)\//', str_replace('\\', '/', $e->getFile()), $match)) {
  168. return $match[1];
  169. }
  170. foreach ($e->getTrace() as $line) {
  171. if (
  172. isset($line['class']) &&
  173. preg_match('/\w+?_plugin_(\w+)/', $line['class'], $match)
  174. ) {
  175. return $match[1];
  176. }
  177. if (
  178. isset($line['file']) &&
  179. preg_match('/lib\/plugins\/(\w+)\//', str_replace('\\', '/', $line['file']), $match)
  180. ) {
  181. return $match[1];
  182. }
  183. }
  184. return false;
  185. }
  186. }