w/s changes
[lhc/web/wiklou.git] / includes / Exception.php
1 <?php
2 /**
3 * Exception class and handler
4 *
5 * @file
6 */
7
8 /**
9 * @defgroup Exception Exception
10 */
11
12 /**
13 * MediaWiki exception
14 *
15 * @ingroup Exception
16 */
17 class MWException extends Exception {
18 /**
19 * Should the exception use $wgOut to output the error ?
20 * @return bool
21 */
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'] );
27 }
28
29 /**
30 * Can the extension use wfMsg() to get i18n messages ?
31 * @return bool
32 */
33 function useMessageCache() {
34 global $wgLang;
35 foreach ( $this->getTrace() as $frame ) {
36 if ( isset( $frame['class'] ) && $frame['class'] === 'LocalisationCache' ) {
37 return false;
38 }
39 }
40 return is_object( $wgLang );
41 }
42
43 /**
44 * Run hook to allow extensions to modify the text of the exception
45 *
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
49 */
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 );
58
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;
64 }
65 if( is_string( $result ) )
66 return $result;
67 }
68 }
69
70 /**
71 * Get a message from i18n
72 *
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
78 */
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 );
85 }
86 }
87
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.
92 *
93 * @return String html to output
94 */
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>";
105 }
106 }
107
108 /**
109 * If $wgShowExceptionDetails is true, return a text message with a
110 * backtrace to the error.
111 */
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";
120 }
121 }
122
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";
130 }
131 }
132
133 /**
134 * Return the requested URL and point to file and line number from which the
135 * exception occured
136 *
137 * @return String
138 */
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]';
148 }
149 } else {
150 $url = '[no req]';
151 }
152
153 return "$url Exception from line $line of $file: $message";
154 }
155
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() );
170 }
171 $wgOut->output();
172 } else {
173 if( $hookResult = $this->runHooks( get_class( $this ) . "Raw" ) ) {
174 die( $hookResult );
175 }
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();
182 }
183 }
184 }
185
186 /**
187 * Output a report about the exception and takes care of formatting.
188 * It will be either HTML or plain text based on isCommandLine().
189 */
190 function report() {
191 $log = $this->getLogMessage();
192 if ( $log ) {
193 wfDebugLog( 'exception', $log );
194 }
195 if ( self::isCommandLine() ) {
196 wfPrintError( $this->getText() );
197 } else {
198 $this->reportHTML();
199 }
200 }
201
202 /**
203 * Send headers and output the beginning of the html page if not using
204 * $wgOut to output the exception.
205 */
206 function htmlHeader() {
207 global $wgLogo, $wgOutputEncoding;
208
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' );
215 }
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>
223 ";
224 }
225
226 /**
227 * print the end of the html page if not using $wgOut.
228 */
229 function htmlFooter() {
230 return "</body></html>";
231 }
232
233 /**
234 * headers handled by subclass?
235 */
236 function htmlBodyOnly() {
237 return false;
238 }
239
240 static function isCommandLine() {
241 return !empty( $GLOBALS['wgCommandLineMode'] ) && !defined( 'MEDIAWIKI_INSTALL' );
242 }
243 }
244
245 /**
246 * Exception class which takes an HTML error message, and does not
247 * produce a backtrace. Replacement for OutputPage::fatalError().
248 * @ingroup Exception
249 */
250 class FatalError extends MWException {
251 function getHTML() {
252 return $this->getMessage();
253 }
254
255 function getText() {
256 return $this->getMessage();
257 }
258 }
259
260 /**
261 * @ingroup Exception
262 */
263 class ErrorPageError extends MWException {
264 public $title, $msg;
265
266 /**
267 * Note: these arguments are keys into wfMsg(), not text!
268 */
269 function __construct( $title, $msg ) {
270 $this->title = $title;
271 $this->msg = $msg;
272 parent::__construct( wfMsg( $msg ) );
273 }
274
275 function report() {
276 global $wgOut;
277 $wgOut->showErrorPage( $this->title, $this->msg );
278 $wgOut->output();
279 }
280 }
281
282 /**
283 * Install an exception handler for MediaWiki exception types.
284 */
285 function wfInstallExceptionHandler() {
286 set_exception_handler( 'wfExceptionHandler' );
287 }
288
289 /**
290 * Report an exception to the user
291 */
292 function wfReportException( Exception $e ) {
293 global $wgShowExceptionDetails;
294
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.";
311 }
312 $message .= "\n";
313 if ( $cmdLine ) {
314 wfPrintError( $message );
315 } else {
316 echo nl2br( htmlspecialchars( $message ) ). "\n";
317 }
318 }
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";
324 }
325 if ( $cmdLine ) {
326 wfPrintError( $message );
327 } else {
328 echo nl2br( htmlspecialchars( $message ) ). "\n";
329 }
330 }
331 }
332
333 /**
334 * Print a message, if possible to STDERR.
335 * Use this in command line mode only (see isCommandLine)
336 */
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 );
344 }
345 }
346
347 /**
348 * Exception handler which simulates the appropriate catch() handling:
349 *
350 * try {
351 * ...
352 * } catch ( MWException $e ) {
353 * $e->report();
354 * } catch ( Exception $e ) {
355 * echo $e->__toString();
356 * }
357 */
358 function wfExceptionHandler( $e ) {
359 global $wgFullyInitialised;
360 wfReportException( $e );
361
362 // Final cleanup, similar to wfErrorExit()
363 if ( $wgFullyInitialised ) {
364 try {
365 wfLogProfilingData(); // uses $wgRequest, hence the $wgFullyInitialised condition
366 } catch ( Exception $e ) {}
367 }
368
369 // Exit value should be nonzero for the benefit of shell jobs
370 exit( 1 );
371 }