(bug 24898) MediaWiki uses /tmp even if a vHost-specific tempdir is set, also make...
[mediawiki.git] / includes / Exception.php
blob0b77322db72f07771580b2415e5920f148e2fcbc
1 <?php
2 /**
3 * Exception class and handler
5 * @file
6 */
8 /**
9 * @defgroup Exception Exception
12 /**
13 * MediaWiki exception
15 * @ingroup Exception
17 class MWException extends Exception {
18 /**
19 * Should the exception use $wgOut to output the error ?
20 * @return bool
22 function useOutputPage() {
23 return $this->useMessageCache() &&
24 !empty( $GLOBALS['wgFullyInitialised'] ) &&
25 ( !empty( $GLOBALS['wgArticle'] ) || ( !empty( $GLOBALS['wgOut'] ) && !$GLOBALS['wgOut']->isArticle() ) ) &&
26 !empty( $GLOBALS['wgTitle'] );
29 /**
30 * Can the extension use wfMsg() to get i18n messages ?
31 * @return bool
33 function useMessageCache() {
34 global $wgLang;
35 foreach ( $this->getTrace() as $frame ) {
36 if ( isset( $frame['class'] ) && $frame['class'] === 'LocalisationCache' ) {
37 return false;
40 return is_object( $wgLang );
43 /**
44 * Run hook to allow extensions to modify the text of the exception
46 * @param $name String: class name of the exception
47 * @param $args Array: arguments to pass to the callback functions
48 * @return Mixed: string to output or null if any hook has been called
50 function runHooks( $name, $args = array() ) {
51 global $wgExceptionHooks;
52 if( !isset( $wgExceptionHooks ) || !is_array( $wgExceptionHooks ) )
53 return; // Just silently ignore
54 if( !array_key_exists( $name, $wgExceptionHooks ) || !is_array( $wgExceptionHooks[ $name ] ) )
55 return;
56 $hooks = $wgExceptionHooks[ $name ];
57 $callargs = array_merge( array( $this ), $args );
59 foreach( $hooks as $hook ) {
60 if( is_string( $hook ) || ( is_array( $hook ) && count( $hook ) >= 2 && is_string( $hook[0] ) ) ) { //'function' or array( 'class', hook' )
61 $result = call_user_func_array( $hook, $callargs );
62 } else {
63 $result = null;
65 if( is_string( $result ) )
66 return $result;
70 /**
71 * Get a message from i18n
73 * @param $key String: message name
74 * @param $fallback String: default message if the message cache can't be
75 * called by the exception
76 * The function also has other parameters that are arguments for the message
77 * @return String message with arguments replaced
79 function msg( $key, $fallback /*[, params...] */ ) {
80 $args = array_slice( func_get_args(), 2 );
81 if ( $this->useMessageCache() ) {
82 return wfMsgReal( $key, $args );
83 } else {
84 return wfMsgReplaceArgs( $fallback, $args );
88 /**
89 * If $wgShowExceptionDetails is true, return a HTML message with a
90 * backtrace to the error, otherwise show a message to ask to set it to true
91 * to show that information.
93 * @return String html to output
95 function getHTML() {
96 global $wgShowExceptionDetails;
97 if( $wgShowExceptionDetails ) {
98 return '<p>' . nl2br( htmlspecialchars( $this->getMessage() ) ) .
99 '</p><p>Backtrace:</p><p>' . nl2br( htmlspecialchars( $this->getTraceAsString() ) ) .
100 "</p>\n";
101 } else {
102 return "<p>Set <b><tt>\$wgShowExceptionDetails = true;</tt></b> " .
103 "at the bottom of LocalSettings.php to show detailed " .
104 "debugging information.</p>";
109 * If $wgShowExceptionDetails is true, return a text message with a
110 * backtrace to the error.
112 function getText() {
113 global $wgShowExceptionDetails;
114 if( $wgShowExceptionDetails ) {
115 return $this->getMessage() .
116 "\nBacktrace:\n" . $this->getTraceAsString() . "\n";
117 } else {
118 return "Set \$wgShowExceptionDetails = true; " .
119 "in LocalSettings.php to show detailed debugging information.\n";
123 /* Return titles of this error page */
124 function getPageTitle() {
125 if ( $this->useMessageCache() ) {
126 return wfMsg( 'internalerror' );
127 } else {
128 global $wgSitename;
129 return "$wgSitename error";
134 * Return the requested URL and point to file and line number from which the
135 * exception occured
137 * @return String
139 function getLogMessage() {
140 global $wgRequest;
141 $file = $this->getFile();
142 $line = $this->getLine();
143 $message = $this->getMessage();
144 if ( isset( $wgRequest ) ) {
145 $url = $wgRequest->getRequestURL();
146 if ( !$url ) {
147 $url = '[no URL]';
149 } else {
150 $url = '[no req]';
153 return "$url Exception from line $line of $file: $message";
156 /** Output the exception report using HTML */
157 function reportHTML() {
158 global $wgOut;
159 if ( $this->useOutputPage() ) {
160 $wgOut->setPageTitle( $this->getPageTitle() );
161 $wgOut->setRobotPolicy( "noindex,nofollow" );
162 $wgOut->setArticleRelated( false );
163 $wgOut->enableClientCache( false );
164 $wgOut->redirect( '' );
165 $wgOut->clearHTML();
166 if( $hookResult = $this->runHooks( get_class( $this ) ) ) {
167 $wgOut->addHTML( $hookResult );
168 } else {
169 $wgOut->addHTML( $this->getHTML() );
171 $wgOut->output();
172 } else {
173 if( $hookResult = $this->runHooks( get_class( $this ) . "Raw" ) ) {
174 die( $hookResult );
176 if ( defined( 'MEDIAWIKI_INSTALL' ) || $this->htmlBodyOnly() ) {
177 echo $this->getHTML();
178 } else {
179 echo $this->htmlHeader();
180 echo $this->getHTML();
181 echo $this->htmlFooter();
187 * Output a report about the exception and takes care of formatting.
188 * It will be either HTML or plain text based on isCommandLine().
190 function report() {
191 $log = $this->getLogMessage();
192 if ( $log ) {
193 wfDebugLog( 'exception', $log );
195 if ( self::isCommandLine() ) {
196 wfPrintError( $this->getText() );
197 } else {
198 $this->reportHTML();
203 * Send headers and output the beginning of the html page if not using
204 * $wgOut to output the exception.
206 function htmlHeader() {
207 global $wgLogo, $wgOutputEncoding;
209 if ( !headers_sent() ) {
210 header( 'HTTP/1.0 500 Internal Server Error' );
211 header( 'Content-type: text/html; charset='.$wgOutputEncoding );
212 /* Don't cache error pages! They cause no end of trouble... */
213 header( 'Cache-control: none' );
214 header( 'Pragma: nocache' );
216 $title = $this->getPageTitle();
217 return "<html>
218 <head>
219 <title>$title</title>
220 </head>
221 <body>
222 <h1><img src='$wgLogo' style='float:left;margin-right:1em' alt=''/>$title</h1>
227 * print the end of the html page if not using $wgOut.
229 function htmlFooter() {
230 return "</body></html>";
234 * headers handled by subclass?
236 function htmlBodyOnly() {
237 return false;
240 static function isCommandLine() {
241 return !empty( $GLOBALS['wgCommandLineMode'] ) && !defined( 'MEDIAWIKI_INSTALL' );
246 * Exception class which takes an HTML error message, and does not
247 * produce a backtrace. Replacement for OutputPage::fatalError().
248 * @ingroup Exception
250 class FatalError extends MWException {
251 function getHTML() {
252 return $this->getMessage();
255 function getText() {
256 return $this->getMessage();
261 * @ingroup Exception
263 class ErrorPageError extends MWException {
264 public $title, $msg;
267 * Note: these arguments are keys into wfMsg(), not text!
269 function __construct( $title, $msg ) {
270 $this->title = $title;
271 $this->msg = $msg;
272 parent::__construct( wfMsg( $msg ) );
275 function report() {
276 global $wgOut;
277 $wgOut->showErrorPage( $this->title, $this->msg );
278 $wgOut->output();
283 * Install an exception handler for MediaWiki exception types.
285 function wfInstallExceptionHandler() {
286 set_exception_handler( 'wfExceptionHandler' );
290 * Report an exception to the user
292 function wfReportException( Exception $e ) {
293 global $wgShowExceptionDetails;
295 $cmdLine = MWException::isCommandLine();
296 if ( $e instanceof MWException ) {
297 try {
298 $e->report();
299 } catch ( Exception $e2 ) {
300 // Exception occurred from within exception handler
301 // Show a simpler error message for the original exception,
302 // don't try to invoke report()
303 $message = "MediaWiki internal error.\n\n";
304 if ( $wgShowExceptionDetails ) {
305 $message .= 'Original exception: ' . $e->__toString() . "\n\n" .
306 'Exception caught inside exception handler: ' . $e2->__toString();
307 } else {
308 $message .= "Exception caught inside exception handler.\n\n" .
309 "Set \$wgShowExceptionDetails = true; at the bottom of LocalSettings.php " .
310 "to show detailed debugging information.";
312 $message .= "\n";
313 if ( $cmdLine ) {
314 wfPrintError( $message );
315 } else {
316 echo nl2br( htmlspecialchars( $message ) ). "\n";
319 } else {
320 $message = "Unexpected non-MediaWiki exception encountered, of type \"" . get_class( $e ) . "\"\n" .
321 $e->__toString() . "\n";
322 if ( $wgShowExceptionDetails ) {
323 $message .= "\n" . $e->getTraceAsString() ."\n";
325 if ( $cmdLine ) {
326 wfPrintError( $message );
327 } else {
328 echo nl2br( htmlspecialchars( $message ) ). "\n";
334 * Print a message, if possible to STDERR.
335 * Use this in command line mode only (see isCommandLine)
337 function wfPrintError( $message ) {
338 #NOTE: STDERR may not be available, especially if php-cgi is used from the command line (bug #15602).
339 # Try to produce meaningful output anyway. Using echo may corrupt output to STDOUT though.
340 if ( defined( 'STDERR' ) ) {
341 fwrite( STDERR, $message );
342 } else {
343 echo( $message );
348 * Exception handler which simulates the appropriate catch() handling:
350 * try {
351 * ...
352 * } catch ( MWException $e ) {
353 * $e->report();
354 * } catch ( Exception $e ) {
355 * echo $e->__toString();
358 function wfExceptionHandler( $e ) {
359 global $wgFullyInitialised;
360 wfReportException( $e );
362 // Final cleanup, similar to wfErrorExit()
363 if ( $wgFullyInitialised ) {
364 try {
365 wfLogProfilingData(); // uses $wgRequest, hence the $wgFullyInitialised condition
366 } catch ( Exception $e ) {}
369 // Exit value should be nonzero for the benefit of shell jobs
370 exit( 1 );