MediaWiki REL1_35
MWExceptionRenderer.php
Go to the documentation of this file.
1<?php
22use Wikimedia\AtEase;
26
32 public const AS_RAW = 1; // show as text
33 public const AS_PRETTY = 2; // show as HTML
34
40 public static function output( Throwable $e, $mode, Throwable $eNew = null ) {
42
43 if ( function_exists( 'apache_setenv' ) ) {
44 // The client should not be blocked on "post-send" updates. If apache decides that
45 // a response should be gzipped, it will wait for PHP to finish since it cannot gzip
46 // anything until it has the full response (even with "Transfer-Encoding: chunked").
47 AtEase\AtEase::suppressWarnings();
48 apache_setenv( 'no-gzip', '1' );
49 AtEase\AtEase::restoreWarnings();
50 }
51
52 if ( defined( 'MW_API' ) ) {
53 self::header( 'MediaWiki-API-Error: internal_api_error_' . get_class( $e ) );
54 }
55
56 if ( self::isCommandLine() ) {
57 self::printError( self::getText( $e ) );
58 } elseif ( $mode === self::AS_PRETTY ) {
59 self::statusHeader( 500 );
60 self::header( "Content-Type: $wgMimeType; charset=UTF-8" );
61 ob_start();
62 if ( $e instanceof DBConnectionError ) {
64 } else {
65 self::reportHTML( $e );
66 }
67 self::header( "Content-Length: " . ob_get_length() );
68 ob_end_flush();
69 } else {
70 ob_start();
71 self::statusHeader( 500 );
72 self::header( "Content-Type: $wgMimeType; charset=UTF-8" );
73 if ( $eNew ) {
74 $message = "MediaWiki internal error.\n\n";
76 $message .= 'Original exception: ' .
77 MWExceptionHandler::getLogMessage( $e ) .
78 "\nBacktrace:\n" . MWExceptionHandler::getRedactedTraceAsString( $e ) .
79 "\n\nException caught inside exception handler: " .
80 MWExceptionHandler::getLogMessage( $eNew ) .
81 "\nBacktrace:\n" . MWExceptionHandler::getRedactedTraceAsString( $eNew );
82 } else {
83 $message .= 'Original exception: ' .
84 MWExceptionHandler::getPublicLogMessage( $e );
85 $message .= "\n\nException caught inside exception handler.\n\n" .
87 }
88 $message .= "\n";
89 } elseif ( $wgShowExceptionDetails ) {
90 $message = MWExceptionHandler::getLogMessage( $e ) .
91 "\nBacktrace:\n" .
92 MWExceptionHandler::getRedactedTraceAsString( $e ) . "\n";
93 } else {
94 $message = MWExceptionHandler::getPublicLogMessage( $e );
95 }
96 print nl2br( htmlspecialchars( $message ) ) . "\n";
97 self::header( "Content-Length: " . ob_get_length() );
98 ob_end_flush();
99 }
100 }
101
106 private static function useOutputPage( Throwable $e ) {
107 // Can the extension use the Message class/wfMessage to get i18n-ed messages?
108 foreach ( $e->getTrace() as $frame ) {
109 if ( isset( $frame['class'] ) && $frame['class'] === LocalisationCache::class ) {
110 return false;
111 }
112 }
113
114 // Don't even bother with OutputPage if there's no Title context set,
115 // (e.g. we're in RL code on load.php) - the Skin system (and probably
116 // most of MediaWiki) won't work.
117
118 return (
119 !empty( $GLOBALS['wgFullyInitialised'] ) &&
120 !empty( $GLOBALS['wgOut'] ) &&
121 RequestContext::getMain()->getTitle() &&
122 !defined( 'MEDIAWIKI_INSTALL' ) &&
123 // Don't send a skinned HTTP 500 page to API clients.
124 !defined( 'MW_API' )
125 );
126 }
127
133 private static function reportHTML( Throwable $e ) {
134 global $wgOut, $wgSitename;
135
136 if ( self::useOutputPage( $e ) ) {
137 if ( $e instanceof MWException ) {
138 $wgOut->prepareErrorPage( $e->getPageTitle() );
139 } elseif ( $e instanceof DBReadOnlyError ) {
140 $wgOut->prepareErrorPage( self::msg( 'readonly', 'Database is locked' ) );
141 } elseif ( $e instanceof DBExpectedError ) {
142 $wgOut->prepareErrorPage( self::msg( 'databaseerror', 'Database error' ) );
143 } else {
144 $wgOut->prepareErrorPage( self::msg( 'internalerror', 'Internal error' ) );
145 }
146
147 // Show any custom GUI message before the details
148 if ( $e instanceof MessageSpecifier ) {
149 $wgOut->addHTML( Html::element( 'p', [], Message::newFromSpecifier( $e )->text() ) );
150 }
151 $wgOut->addHTML( self::getHTML( $e ) );
152
153 $wgOut->output();
154 } else {
155 self::header( 'Content-Type: text/html; charset=utf-8' );
156 $pageTitle = self::msg( 'internalerror', 'Internal error' );
157 echo "<!DOCTYPE html>\n" .
158 '<html><head>' .
159 // Mimick OutputPage::setPageTitle behaviour
160 '<title>' .
161 htmlspecialchars( self::msg( 'pagetitle', "$1 - $wgSitename", $pageTitle ) ) .
162 '</title>' .
163 '<style>body { font-family: sans-serif; margin: 0; padding: 0.5em 2em; }</style>' .
164 "</head><body>\n";
165
166 echo self::getHTML( $e );
167
168 echo "</body></html>\n";
169 }
170 }
171
180 public static function getHTML( Throwable $e ) {
182
184 $html = "<div class=\"errorbox mw-content-ltr\"><p>" .
185 nl2br( htmlspecialchars( MWExceptionHandler::getLogMessage( $e ) ) ) .
186 '</p><p>Backtrace:</p><p>' .
187 nl2br( htmlspecialchars( MWExceptionHandler::getRedactedTraceAsString( $e ) ) ) .
188 "</p></div>\n";
189 } else {
190 $logId = WebRequest::getRequestId();
191 $html = "<div class=\"errorbox mw-content-ltr\">" .
192 htmlspecialchars(
193 '[' . $logId . '] ' .
194 gmdate( 'Y-m-d H:i:s' ) . ": " .
195 self::msg( "internalerror-fatal-exception",
196 "Fatal exception of type $1",
197 get_class( $e ),
198 $logId,
199 MWExceptionHandler::getURL()
200 ) ) . "</div>\n" .
201 "<!-- " . wordwrap( self::getShowBacktraceError( $e ), 50 ) . " -->";
202 }
203
204 return $html;
205 }
206
216 private static function msg( $key, $fallback, ...$params ) {
217 global $wgSitename;
218
219 // FIXME: Keep logic in sync with MWException::msg.
220 try {
221 $res = wfMessage( $key, ...$params )->text();
222 } catch ( Exception $e ) {
223 $res = wfMsgReplaceArgs( $fallback, $params );
224 // If an exception happens inside message rendering,
225 // {{SITENAME}} sometimes won't be replaced.
226 $res = strtr( $res, [
227 '{{SITENAME}}' => $wgSitename,
228 ] );
229 }
230 return $res;
231 }
232
237 private static function getText( Throwable $e ) {
239
241 return MWExceptionHandler::getLogMessage( $e ) .
242 "\nBacktrace:\n" .
243 MWExceptionHandler::getRedactedTraceAsString( $e ) . "\n";
244 } else {
245 return self::getShowBacktraceError( $e ) . "\n";
246 }
247 }
248
253 private static function getShowBacktraceError( Throwable $e ) {
254 $var = '$wgShowExceptionDetails = true;';
255 return "Set $var at the bottom of LocalSettings.php to show detailed debugging information.";
256 }
257
261 private static function isCommandLine() {
262 return !empty( $GLOBALS['wgCommandLineMode'] );
263 }
264
268 private static function header( $header ) {
269 if ( !headers_sent() ) {
270 header( $header );
271 }
272 }
273
277 private static function statusHeader( $code ) {
278 if ( !headers_sent() ) {
279 HttpStatus::header( $code );
280 }
281 }
282
290 private static function printError( $message ) {
291 // NOTE: STDERR may not be available, especially if php-cgi is used from the
292 // command line (T17602). Try to produce meaningful output anyway. Using
293 // echo may corrupt output to STDOUT though.
294 if ( defined( 'STDERR' ) ) {
295 fwrite( STDERR, $message );
296 } else {
297 echo $message;
298 }
299 }
300
304 private static function reportOutageHTML( Throwable $e ) {
306
307 $sorry = htmlspecialchars( self::msg(
308 'dberr-problems',
309 'Sorry! This site is experiencing technical difficulties.'
310 ) );
311 $again = htmlspecialchars( self::msg(
312 'dberr-again',
313 'Try waiting a few minutes and reloading.'
314 ) );
315
316 if ( $wgShowHostnames ) {
317 $info = str_replace(
318 '$1',
319 Html::element( 'span', [ 'dir' => 'ltr' ], $e->getMessage() ),
320 htmlspecialchars( self::msg( 'dberr-info', '($1)' ) )
321 );
322 } else {
323 $info = htmlspecialchars( self::msg(
324 'dberr-info-hidden',
325 '(Cannot access the database)'
326 ) );
327 }
328
329 MediaWikiServices::getInstance()->getMessageCache()->disable(); // no DB access
330 $html = "<!DOCTYPE html>\n" .
331 '<html><head>' .
332 '<title>' .
333 htmlspecialchars( $wgSitename ) .
334 '</title>' .
335 '<style>body { font-family: sans-serif; margin: 0; padding: 0.5em 2em; }</style>' .
336 "</head><body><h1>$sorry</h1><p>$again</p><p><small>$info</small></p>";
337
339 $html .= '<p>Backtrace:</p><pre>' .
340 htmlspecialchars( $e->getTraceAsString() ) . '</pre>';
341 }
342
343 $html .= '</body></html>';
344 echo $html;
345 }
346}
$GLOBALS['IP']
$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:786
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 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:452
Base class for the more common types of database errors.
while(( $__line=Maintenance::readconsole()) !==false) print
Definition eval.php:64
Stable for implementing.
$header