MediaWiki REL1_36
MWExceptionRenderer.php
Go to the documentation of this file.
1<?php
22use Wikimedia\AtEase;
26use Wikimedia\RequestTimeout\RequestTimeoutException;
27
33 public const AS_RAW = 1; // show as text
34 public const AS_PRETTY = 2; // show as HTML
35
41 public static function output( Throwable $e, $mode, Throwable $eNew = null ) {
43
44 if ( function_exists( 'apache_setenv' ) ) {
45 // The client should not be blocked on "post-send" updates. If apache decides that
46 // a response should be gzipped, it will wait for PHP to finish since it cannot gzip
47 // anything until it has the full response (even with "Transfer-Encoding: chunked").
48 AtEase\AtEase::suppressWarnings();
49 apache_setenv( 'no-gzip', '1' );
50 AtEase\AtEase::restoreWarnings();
51 }
52
53 if ( defined( 'MW_API' ) ) {
54 self::header( 'MediaWiki-API-Error: internal_api_error_' . get_class( $e ) );
55 }
56
57 if ( self::isCommandLine() ) {
58 self::printError( self::getText( $e ) );
59 } elseif ( $mode === self::AS_PRETTY ) {
60 self::statusHeader( 500 );
61 self::header( "Content-Type: $wgMimeType; charset=UTF-8" );
62 ob_start();
63 if ( $e instanceof DBConnectionError ) {
65 } else {
66 self::reportHTML( $e );
67 }
68 self::header( "Content-Length: " . ob_get_length() );
69 ob_end_flush();
70 } else {
71 ob_start();
72 self::statusHeader( 500 );
73 self::header( "Content-Type: $wgMimeType; charset=UTF-8" );
74 if ( $eNew ) {
75 $message = "MediaWiki internal error.\n\n";
77 $message .= 'Original exception: ' .
78 MWExceptionHandler::getLogMessage( $e ) .
79 "\nBacktrace:\n" . MWExceptionHandler::getRedactedTraceAsString( $e ) .
80 "\n\nException caught inside exception handler: " .
81 MWExceptionHandler::getLogMessage( $eNew ) .
82 "\nBacktrace:\n" . MWExceptionHandler::getRedactedTraceAsString( $eNew );
83 } else {
84 $message .= 'Original exception: ' .
85 MWExceptionHandler::getPublicLogMessage( $e );
86 $message .= "\n\nException caught inside exception handler.\n\n" .
88 }
89 $message .= "\n";
90 } elseif ( $wgShowExceptionDetails ) {
91 $message = MWExceptionHandler::getLogMessage( $e ) .
92 "\nBacktrace:\n" .
93 MWExceptionHandler::getRedactedTraceAsString( $e ) . "\n";
94 } else {
95 $message = MWExceptionHandler::getPublicLogMessage( $e );
96 }
97 print nl2br( htmlspecialchars( $message ) ) . "\n";
98 self::header( "Content-Length: " . ob_get_length() );
99 ob_end_flush();
100 }
101 }
102
107 private static function useOutputPage( Throwable $e ) {
108 // Can the extension use the Message class/wfMessage to get i18n-ed messages?
109 foreach ( $e->getTrace() as $frame ) {
110 if ( isset( $frame['class'] ) && $frame['class'] === LocalisationCache::class ) {
111 return false;
112 }
113 }
114
115 // Don't even bother with OutputPage if there's no Title context set,
116 // (e.g. we're in RL code on load.php) - the Skin system (and probably
117 // most of MediaWiki) won't work.
118
119 return (
120 !empty( $GLOBALS['wgFullyInitialised'] ) &&
121 !empty( $GLOBALS['wgOut'] ) &&
122 RequestContext::getMain()->getTitle() &&
123 !defined( 'MEDIAWIKI_INSTALL' ) &&
124 // Don't send a skinned HTTP 500 page to API clients.
125 !defined( 'MW_API' )
126 );
127 }
128
134 private static function reportHTML( Throwable $e ) {
135 global $wgOut, $wgSitename;
136
137 if ( self::useOutputPage( $e ) ) {
138 $wgOut->prepareErrorPage( self::getExceptionTitle( $e ) );
139
140 // Show any custom GUI message before the details
141 $customMessage = self::getCustomMessage( $e );
142 if ( $customMessage !== null ) {
143 $wgOut->addHTML( Html::element( 'p', [], $customMessage ) );
144 }
145 $wgOut->addHTML( self::getHTML( $e ) );
146
147 $wgOut->output();
148 } else {
149 self::header( 'Content-Type: text/html; charset=utf-8' );
150 $pageTitle = self::msg( 'internalerror', 'Internal error' );
151 echo "<!DOCTYPE html>\n" .
152 '<html><head>' .
153 // Mimick OutputPage::setPageTitle behaviour
154 '<title>' .
155 htmlspecialchars( self::msg( 'pagetitle', "$1 - $wgSitename", $pageTitle ) ) .
156 '</title>' .
157 '<style>body { font-family: sans-serif; margin: 0; padding: 0.5em 2em; }</style>' .
158 "</head><body>\n";
159
160 echo self::getHTML( $e );
161
162 echo "</body></html>\n";
163 }
164 }
165
174 public static function getHTML( Throwable $e ) {
176
178 $html = "<div class=\"errorbox mw-content-ltr\"><p>" .
179 nl2br( htmlspecialchars( MWExceptionHandler::getLogMessage( $e ) ) ) .
180 '</p><p>Backtrace:</p><p>' .
181 nl2br( htmlspecialchars( MWExceptionHandler::getRedactedTraceAsString( $e ) ) ) .
182 "</p></div>\n";
183 } else {
184 $logId = WebRequest::getRequestId();
185 $html = "<div class=\"errorbox mw-content-ltr\">" .
186 htmlspecialchars(
187 '[' . $logId . '] ' .
188 gmdate( 'Y-m-d H:i:s' ) . ": " .
189 self::msg( "internalerror-fatal-exception",
190 "Fatal exception of type $1",
191 get_class( $e ),
192 $logId,
193 MWExceptionHandler::getURL()
194 ) ) . "</div>\n" .
195 "<!-- " . wordwrap( self::getShowBacktraceError( $e ), 50 ) . " -->";
196 }
197
198 return $html;
199 }
200
210 private static function msg( $key, $fallback, ...$params ) {
211 global $wgSitename;
212
213 // FIXME: Keep logic in sync with MWException::msg.
214 try {
215 $res = wfMessage( $key, ...$params )->text();
216 } catch ( Exception $e ) {
217 $res = wfMsgReplaceArgs( $fallback, $params );
218 // If an exception happens inside message rendering,
219 // {{SITENAME}} sometimes won't be replaced.
220 $res = strtr( $res, [
221 '{{SITENAME}}' => $wgSitename,
222 ] );
223 }
224 return $res;
225 }
226
231 private static function getText( Throwable $e ) {
233
235 return MWExceptionHandler::getLogMessage( $e ) .
236 "\nBacktrace:\n" .
237 MWExceptionHandler::getRedactedTraceAsString( $e ) . "\n";
238 } else {
239 return self::getShowBacktraceError( $e ) . "\n";
240 }
241 }
242
247 private static function getShowBacktraceError( Throwable $e ) {
248 $var = '$wgShowExceptionDetails = true;';
249 return "Set $var at the bottom of LocalSettings.php to show detailed debugging information.";
250 }
251
258 private static function getExceptionTitle( Throwable $e ) {
259 if ( $e instanceof MWException ) {
260 return $e->getPageTitle();
261 } elseif ( $e instanceof DBReadOnlyError ) {
262 return self::msg( 'readonly', 'Database is locked' );
263 } elseif ( $e instanceof DBExpectedError ) {
264 return self::msg( 'databaseerror', 'Database error' );
265 } elseif ( $e instanceof RequestTimeoutException ) {
266 return self::msg( 'timeouterror', 'Request timeout' );
267 } else {
268 return self::msg( 'internalerror', 'Internal error' );
269 }
270 }
271
279 private static function getCustomMessage( Throwable $e ) {
280 try {
281 if ( $e instanceof MessageSpecifier ) {
282 $msg = Message::newFromSpecifier( $e );
283 } elseif ( $e instanceof RequestTimeoutException ) {
284 $msg = wfMessage( 'timeouterror-text', $e->getLimit() );
285 } else {
286 return null;
287 }
288 $text = $msg->text();
289 } catch ( Exception $e2 ) {
290 return null;
291 }
292 return $text;
293 }
294
298 private static function isCommandLine() {
299 return !empty( $GLOBALS['wgCommandLineMode'] );
300 }
301
305 private static function header( $header ) {
306 if ( !headers_sent() ) {
307 header( $header );
308 }
309 }
310
314 private static function statusHeader( $code ) {
315 if ( !headers_sent() ) {
316 HttpStatus::header( $code );
317 }
318 }
319
327 private static function printError( $message ) {
328 // NOTE: STDERR may not be available, especially if php-cgi is used from the
329 // command line (T17602). Try to produce meaningful output anyway. Using
330 // echo may corrupt output to STDOUT though.
331 if ( defined( 'STDERR' ) ) {
332 fwrite( STDERR, $message );
333 } else {
334 echo $message;
335 }
336 }
337
341 private static function reportOutageHTML( Throwable $e ) {
343
344 $sorry = htmlspecialchars( self::msg(
345 'dberr-problems',
346 'Sorry! This site is experiencing technical difficulties.'
347 ) );
348 $again = htmlspecialchars( self::msg(
349 'dberr-again',
350 'Try waiting a few minutes and reloading.'
351 ) );
352
353 if ( $wgShowHostnames ) {
354 $info = str_replace(
355 '$1',
356 Html::element( 'span', [ 'dir' => 'ltr' ], $e->getMessage() ),
357 htmlspecialchars( self::msg( 'dberr-info', '($1)' ) )
358 );
359 } else {
360 $info = htmlspecialchars( self::msg(
361 'dberr-info-hidden',
362 '(Cannot access the database)'
363 ) );
364 }
365
366 MediaWikiServices::getInstance()->getMessageCache()->disable(); // no DB access
367 $html = "<!DOCTYPE html>\n" .
368 '<html><head>' .
369 '<title>' .
370 htmlspecialchars( $wgSitename ) .
371 '</title>' .
372 '<style>body { font-family: sans-serif; margin: 0; padding: 0.5em 2em; }</style>' .
373 "</head><body><h1>$sorry</h1><p>$again</p><p><small>$info</small></p>";
374
376 $html .= '<p>Backtrace:</p><pre>' .
377 htmlspecialchars( $e->getTraceAsString() ) . '</pre>';
378 }
379
380 $html .= '</body></html>';
381 echo $html;
382 }
383}
$wgMimeType
The default Content-Type header.
$wgShowHostnames
Expose backend server host names through the API and various HTML comments.
$wgSitename
Name of the site.
$wgShowExceptionDetails
If set to true, uncaught exceptions will print the exception message and a complete stack trace to ou...
wfMsgReplaceArgs( $message, $args)
Replace message parameter keys on the given formatted output.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
$fallback
$wgOut
Definition Setup.php:818
Class to expose exceptions to the client (API bots, users, admins using CLI scripts)
static getText(Throwable $e)
static printError( $message)
Print a message, if possible to STDERR.
static reportOutageHTML(Throwable $e)
static useOutputPage(Throwable $e)
static getCustomMessage(Throwable $e)
Extract an additional user-visible message from an exception, or null if it has none.
static getExceptionTitle(Throwable $e)
Get the page title to be used for a given exception.
static msg( $key, $fallback,... $params)
Get a message from i18n.
static getHTML(Throwable $e)
If $wgShowExceptionDetails is true, return a HTML message with a backtrace to the error,...
static output(Throwable $e, $mode, Throwable $eNew=null)
static getShowBacktraceError(Throwable $e)
static reportHTML(Throwable $e)
Output the throwable report using HTML.
MediaWiki exception.
MediaWikiServices is the service locator for the application scope of MediaWiki.
static newFromSpecifier( $value)
Transform a MessageSpecifier or a primitive value used interchangeably with specifiers (a message key...
Definition Message.php:415
Base class for the more common types of database errors.
while(( $__line=Maintenance::readconsole()) !==false) print
Definition eval.php:69
$header