Exception.php 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. <?php
  2. /**
  3. * @defgroup Exception Exception
  4. */
  5. /**
  6. * MediaWiki exception
  7. * @ingroup Exception
  8. */
  9. class MWException extends Exception {
  10. /**
  11. * Should the exception use $wgOut to output the error ?
  12. * @return bool
  13. */
  14. function useOutputPage() {
  15. return !empty( $GLOBALS['wgFullyInitialised'] ) &&
  16. ( !empty( $GLOBALS['wgArticle'] ) || ( !empty( $GLOBALS['wgOut'] ) && !$GLOBALS['wgOut']->isArticle() ) ) &&
  17. !empty( $GLOBALS['wgTitle'] );
  18. }
  19. /**
  20. * Can the extension use wfMsg() to get i18n messages ?
  21. * @return bool
  22. */
  23. function useMessageCache() {
  24. global $wgLang;
  25. return is_object( $wgLang );
  26. }
  27. /**
  28. * Run hook to allow extensions to modify the text of the exception
  29. *
  30. * @param String $name class name of the exception
  31. * @param Array $args arguments to pass to the callback functions
  32. * @return mixed string to output or null if any hook has been called
  33. */
  34. function runHooks( $name, $args = array() ) {
  35. global $wgExceptionHooks;
  36. if( !isset( $wgExceptionHooks ) || !is_array( $wgExceptionHooks ) )
  37. return; // Just silently ignore
  38. if( !array_key_exists( $name, $wgExceptionHooks ) || !is_array( $wgExceptionHooks[ $name ] ) )
  39. return;
  40. $hooks = $wgExceptionHooks[ $name ];
  41. $callargs = array_merge( array( $this ), $args );
  42. foreach( $hooks as $hook ) {
  43. if( is_string( $hook ) || ( is_array( $hook ) && count( $hook ) >= 2 && is_string( $hook[0] ) ) ) { //'function' or array( 'class', hook' )
  44. $result = call_user_func_array( $hook, $callargs );
  45. } else {
  46. $result = null;
  47. }
  48. if( is_string( $result ) )
  49. return $result;
  50. }
  51. }
  52. /**
  53. * Get a message from i18n
  54. *
  55. * @param String $key message name
  56. * @param String $fallback default message if the message cache can't be
  57. * called by the exception
  58. * The function also has other parameters that are arguments for the message
  59. * @return String message with arguments replaced
  60. */
  61. function msg( $key, $fallback /*[, params...] */ ) {
  62. $args = array_slice( func_get_args(), 2 );
  63. if ( $this->useMessageCache() ) {
  64. return wfMsgReal( $key, $args );
  65. } else {
  66. return wfMsgReplaceArgs( $fallback, $args );
  67. }
  68. }
  69. /**
  70. * If $wgShowExceptionDetails is true, return a HTML message with a
  71. * backtrace to the error, otherwise show a message to ask to set it to true
  72. * to show that information.
  73. *
  74. * @return String html to output
  75. */
  76. function getHTML() {
  77. global $wgShowExceptionDetails;
  78. if( $wgShowExceptionDetails ) {
  79. return '<p>' . nl2br( htmlspecialchars( $this->getMessage() ) ) .
  80. '</p><p>Backtrace:</p><p>' . nl2br( htmlspecialchars( $this->getTraceAsString() ) ) .
  81. "</p>\n";
  82. } else {
  83. return "<p>Set <b><tt>\$wgShowExceptionDetails = true;</tt></b> " .
  84. "at the bottom of LocalSettings.php to show detailed " .
  85. "debugging information.</p>";
  86. }
  87. }
  88. /**
  89. * If $wgShowExceptionDetails is true, return a text message with a
  90. * backtrace to the error.
  91. */
  92. function getText() {
  93. global $wgShowExceptionDetails;
  94. if( $wgShowExceptionDetails ) {
  95. return $this->getMessage() .
  96. "\nBacktrace:\n" . $this->getTraceAsString() . "\n";
  97. } else {
  98. return "Set \$wgShowExceptionDetails = true; " .
  99. "in LocalSettings.php to show detailed debugging information.\n";
  100. }
  101. }
  102. /* Return titles of this error page */
  103. function getPageTitle() {
  104. if ( $this->useMessageCache() ) {
  105. return wfMsg( 'internalerror' );
  106. } else {
  107. global $wgSitename;
  108. return "$wgSitename error";
  109. }
  110. }
  111. /**
  112. * Return the requested URL and point to file and line number from which the
  113. * exception occured
  114. *
  115. * @return string
  116. */
  117. function getLogMessage() {
  118. global $wgRequest;
  119. $file = $this->getFile();
  120. $line = $this->getLine();
  121. $message = $this->getMessage();
  122. if ( isset( $wgRequest ) ) {
  123. $url = $wgRequest->getRequestURL();
  124. if ( !$url ) {
  125. $url = '[no URL]';
  126. }
  127. } else {
  128. $url = '[no req]';
  129. }
  130. return "$url Exception from line $line of $file: $message";
  131. }
  132. /** Output the exception report using HTML */
  133. function reportHTML() {
  134. global $wgOut;
  135. if ( $this->useOutputPage() ) {
  136. $wgOut->setPageTitle( $this->getPageTitle() );
  137. $wgOut->setRobotPolicy( "noindex,nofollow" );
  138. $wgOut->setArticleRelated( false );
  139. $wgOut->enableClientCache( false );
  140. $wgOut->redirect( '' );
  141. $wgOut->clearHTML();
  142. if( $hookResult = $this->runHooks( get_class( $this ) ) ) {
  143. $wgOut->addHTML( $hookResult );
  144. } else {
  145. $wgOut->addHTML( $this->getHTML() );
  146. }
  147. $wgOut->output();
  148. } else {
  149. if( $hookResult = $this->runHooks( get_class( $this ) . "Raw" ) ) {
  150. die( $hookResult );
  151. }
  152. if ( defined( 'MEDIAWIKI_INSTALL' ) || $this->htmlBodyOnly() ) {
  153. echo $this->getHTML();
  154. } else {
  155. echo $this->htmlHeader();
  156. echo $this->getHTML();
  157. echo $this->htmlFooter();
  158. }
  159. }
  160. }
  161. /**
  162. * Output a report about the exception and takes care of formatting.
  163. * It will be either HTML or plain text based on isCommandLine().
  164. */
  165. function report() {
  166. $log = $this->getLogMessage();
  167. if ( $log ) {
  168. wfDebugLog( 'exception', $log );
  169. }
  170. if ( self::isCommandLine() ) {
  171. wfPrintError( $this->getText() );
  172. } else {
  173. $this->reportHTML();
  174. }
  175. }
  176. /**
  177. * Send headers and output the beginning of the html page if not using
  178. * $wgOut to output the exception.
  179. */
  180. function htmlHeader() {
  181. global $wgLogo, $wgSitename, $wgOutputEncoding;
  182. if ( !headers_sent() ) {
  183. header( 'HTTP/1.0 500 Internal Server Error' );
  184. header( 'Content-type: text/html; charset='.$wgOutputEncoding );
  185. /* Don't cache error pages! They cause no end of trouble... */
  186. header( 'Cache-control: none' );
  187. header( 'Pragma: nocache' );
  188. }
  189. $title = $this->getPageTitle();
  190. echo "<html>
  191. <head>
  192. <title>$title</title>
  193. </head>
  194. <body>
  195. <h1><img src='$wgLogo' style='float:left;margin-right:1em' alt=''/>$title</h1>
  196. ";
  197. }
  198. /**
  199. * print the end of the html page if not using $wgOut.
  200. */
  201. function htmlFooter() {
  202. echo "</body></html>";
  203. }
  204. /**
  205. * headers handled by subclass?
  206. */
  207. function htmlBodyOnly() {
  208. return false;
  209. }
  210. static function isCommandLine() {
  211. return !empty( $GLOBALS['wgCommandLineMode'] ) && !defined( 'MEDIAWIKI_INSTALL' );
  212. }
  213. }
  214. /**
  215. * Exception class which takes an HTML error message, and does not
  216. * produce a backtrace. Replacement for OutputPage::fatalError().
  217. * @ingroup Exception
  218. */
  219. class FatalError extends MWException {
  220. function getHTML() {
  221. return $this->getMessage();
  222. }
  223. function getText() {
  224. return $this->getMessage();
  225. }
  226. }
  227. /**
  228. * @ingroup Exception
  229. */
  230. class ErrorPageError extends MWException {
  231. public $title, $msg;
  232. /**
  233. * Note: these arguments are keys into wfMsg(), not text!
  234. */
  235. function __construct( $title, $msg ) {
  236. $this->title = $title;
  237. $this->msg = $msg;
  238. parent::__construct( wfMsg( $msg ) );
  239. }
  240. function report() {
  241. global $wgOut;
  242. $wgOut->showErrorPage( $this->title, $this->msg );
  243. $wgOut->output();
  244. }
  245. }
  246. /**
  247. * Install an exception handler for MediaWiki exception types.
  248. */
  249. function wfInstallExceptionHandler() {
  250. set_exception_handler( 'wfExceptionHandler' );
  251. }
  252. /**
  253. * Report an exception to the user
  254. */
  255. function wfReportException( Exception $e ) {
  256. $cmdLine = MWException::isCommandLine();
  257. if ( $e instanceof MWException ) {
  258. try {
  259. $e->report();
  260. } catch ( Exception $e2 ) {
  261. // Exception occurred from within exception handler
  262. // Show a simpler error message for the original exception,
  263. // don't try to invoke report()
  264. $message = "MediaWiki internal error.\n\n";
  265. if ( $GLOBALS['wgShowExceptionDetails'] )
  266. $message .= "Original exception: " . $e->__toString();
  267. $message .= "\n\nException caught inside exception handler";
  268. if ( $GLOBALS['wgShowExceptionDetails'] )
  269. $message .= ": " . $e2->__toString();
  270. $message .= "\n";
  271. if ( $cmdLine ) {
  272. wfPrintError( $message );
  273. } else {
  274. echo nl2br( htmlspecialchars( $message ) ). "\n";
  275. }
  276. }
  277. } else {
  278. $message = "Unexpected non-MediaWiki exception encountered, of type \"" . get_class( $e ) . "\"\n" .
  279. $e->__toString() . "\n";
  280. if ( $GLOBALS['wgShowExceptionDetails'] ) {
  281. $message .= "\n" . $e->getTraceAsString() ."\n";
  282. }
  283. if ( $cmdLine ) {
  284. wfPrintError( $message );
  285. } else {
  286. echo nl2br( htmlspecialchars( $message ) ). "\n";
  287. }
  288. }
  289. }
  290. /**
  291. * Print a message, if possible to STDERR.
  292. * Use this in command line mode only (see isCommandLine)
  293. */
  294. function wfPrintError( $message ) {
  295. #NOTE: STDERR may not be available, especially if php-cgi is used from the command line (bug #15602).
  296. # Try to produce meaningful output anyway. Using echo may corrupt output to STDOUT though.
  297. if ( defined( 'STDERR' ) ) {
  298. fwrite( STDERR, $message );
  299. }
  300. else {
  301. echo( $message );
  302. }
  303. }
  304. /**
  305. * Exception handler which simulates the appropriate catch() handling:
  306. *
  307. * try {
  308. * ...
  309. * } catch ( MWException $e ) {
  310. * $e->report();
  311. * } catch ( Exception $e ) {
  312. * echo $e->__toString();
  313. * }
  314. */
  315. function wfExceptionHandler( $e ) {
  316. global $wgFullyInitialised;
  317. wfReportException( $e );
  318. // Final cleanup, similar to wfErrorExit()
  319. if ( $wgFullyInitialised ) {
  320. try {
  321. wfLogProfilingData(); // uses $wgRequest, hence the $wgFullyInitialised condition
  322. } catch ( Exception $e ) {}
  323. }
  324. // Exit value should be nonzero for the benefit of shell jobs
  325. exit( 1 );
  326. }