MediaWiki  master
MWExceptionRenderer.php
Go to the documentation of this file.
1 <?php
25 
31  const AS_RAW = 1; // show as text
32  const AS_PRETTY = 2; // show as HTML
33 
39  public static function output( $e, $mode, $eNew = null ) {
41 
42  if ( function_exists( 'apache_setenv' ) ) {
43  // The client should not be blocked on "post-send" updates. If apache decides that
44  // a response should be gzipped, it will wait for PHP to finish since it cannot gzip
45  // anything until it has the full response (even with "Transfer-Encoding: chunked").
46  AtEase\AtEase::suppressWarnings();
47  apache_setenv( 'no-gzip', '1' );
48  AtEase\AtEase::restoreWarnings();
49  }
50 
51  if ( defined( 'MW_API' ) ) {
52  // Unhandled API exception, we can't be sure that format printer is alive
53  self::header( 'MediaWiki-API-Error: internal_api_error_' . get_class( $e ) );
54  wfHttpError( 500, 'Internal Server Error', self::getText( $e ) );
55  } elseif ( self::isCommandLine() ) {
56  self::printError( self::getText( $e ) );
57  } elseif ( $mode === self::AS_PRETTY ) {
58  self::statusHeader( 500 );
59  self::header( "Content-Type: $wgMimeType; charset=UTF-8" );
60  ob_start();
61  if ( $e instanceof DBConnectionError ) {
62  self::reportOutageHTML( $e );
63  } else {
64  self::reportHTML( $e );
65  }
66  self::header( "Content-Length: " . ob_get_length() );
67  ob_end_flush();
68  } else {
69  ob_start();
70  self::statusHeader( 500 );
71  self::header( "Content-Type: $wgMimeType; charset=UTF-8" );
72  if ( $eNew ) {
73  $message = "MediaWiki internal error.\n\n";
74  if ( $wgShowExceptionDetails ) {
75  $message .= 'Original exception: ' .
77  "\nBacktrace:\n" . MWExceptionHandler::getRedactedTraceAsString( $e ) .
78  "\n\nException caught inside exception handler: " .
80  "\nBacktrace:\n" . MWExceptionHandler::getRedactedTraceAsString( $eNew );
81  } else {
82  $message .= 'Original exception: ' .
84  $message .= "\n\nException caught inside exception handler.\n\n" .
85  self::getShowBacktraceError( $e );
86  }
87  $message .= "\n";
88  } elseif ( $wgShowExceptionDetails ) {
89  $message = MWExceptionHandler::getLogMessage( $e ) .
90  "\nBacktrace:\n" .
92  } else {
94  }
95  print nl2br( htmlspecialchars( $message ) ) . "\n";
96  self::header( "Content-Length: " . ob_get_length() );
97  ob_end_flush();
98  }
99  }
100 
105  private static function useOutputPage( $e ) {
106  // Can the extension use the Message class/wfMessage to get i18n-ed messages?
107  foreach ( $e->getTrace() as $frame ) {
108  if ( isset( $frame['class'] ) && $frame['class'] === LocalisationCache::class ) {
109  return false;
110  }
111  }
112 
113  // Don't even bother with OutputPage if there's no Title context set,
114  // (e.g. we're in RL code on load.php) - the Skin system (and probably
115  // most of MediaWiki) won't work.
116 
117  return (
118  !empty( $GLOBALS['wgFullyInitialised'] ) &&
119  !empty( $GLOBALS['wgOut'] ) &&
120  RequestContext::getMain()->getTitle() &&
121  !defined( 'MEDIAWIKI_INSTALL' )
122  );
123  }
124 
130  private static function reportHTML( $e ) {
131  global $wgOut, $wgSitename;
132 
133  if ( self::useOutputPage( $e ) ) {
134  if ( $e instanceof MWException ) {
135  $wgOut->prepareErrorPage( $e->getPageTitle() );
136  } elseif ( $e instanceof DBReadOnlyError ) {
137  $wgOut->prepareErrorPage( self::msg( 'readonly', 'Database is locked' ) );
138  } elseif ( $e instanceof DBExpectedError ) {
139  $wgOut->prepareErrorPage( self::msg( 'databaseerror', 'Database error' ) );
140  } else {
141  $wgOut->prepareErrorPage( self::msg( 'internalerror', 'Internal error' ) );
142  }
143 
144  // Show any custom GUI message before the details
145  if ( $e instanceof MessageSpecifier ) {
146  $wgOut->addHTML( Html::element( 'p', [], Message::newFromSpecifier( $e )->text() ) );
147  }
148  $wgOut->addHTML( self::getHTML( $e ) );
149 
150  $wgOut->output();
151  } else {
152  self::header( 'Content-Type: text/html; charset=utf-8' );
153  $pageTitle = self::msg( 'internalerror', 'Internal error' );
154  echo "<!DOCTYPE html>\n" .
155  '<html><head>' .
156  // Mimick OutputPage::setPageTitle behaviour
157  '<title>' .
158  htmlspecialchars( self::msg( 'pagetitle', "$1 - $wgSitename", $pageTitle ) ) .
159  '</title>' .
160  '<style>body { font-family: sans-serif; margin: 0; padding: 0.5em 2em; }</style>' .
161  "</head><body>\n";
162 
163  echo self::getHTML( $e );
164 
165  echo "</body></html>\n";
166  }
167  }
168 
177  public static function getHTML( $e ) {
179 
180  if ( $wgShowExceptionDetails ) {
181  $html = "<div class=\"errorbox mw-content-ltr\"><p>" .
182  nl2br( htmlspecialchars( MWExceptionHandler::getLogMessage( $e ) ) ) .
183  '</p><p>Backtrace:</p><p>' .
184  nl2br( htmlspecialchars( MWExceptionHandler::getRedactedTraceAsString( $e ) ) ) .
185  "</p></div>\n";
186  } else {
187  $logId = WebRequest::getRequestId();
188  $html = "<div class=\"errorbox mw-content-ltr\">" .
189  htmlspecialchars(
190  '[' . $logId . '] ' .
191  gmdate( 'Y-m-d H:i:s' ) . ": " .
192  self::msg( "internalerror-fatal-exception",
193  "Fatal exception of type $1",
194  get_class( $e ),
195  $logId,
197  ) ) . "</div>\n" .
198  "<!-- " . wordwrap( self::getShowBacktraceError( $e ), 50 ) . " -->";
199  }
200 
201  return $html;
202  }
203 
213  private static function msg( $key, $fallback, ...$params ) {
214  global $wgSitename;
215 
216  // FIXME: Keep logic in sync with MWException::msg.
217  try {
218  $res = wfMessage( $key, ...$params )->text();
219  } catch ( Exception $e ) {
220  $res = wfMsgReplaceArgs( $fallback, $params );
221  // If an exception happens inside message rendering,
222  // {{SITENAME}} sometimes won't be replaced.
223  $res = strtr( $res, [
224  '{{SITENAME}}' => $wgSitename,
225  ] );
226  }
227  return $res;
228  }
229 
234  private static function getText( $e ) {
236 
237  if ( $wgShowExceptionDetails ) {
238  return MWExceptionHandler::getLogMessage( $e ) .
239  "\nBacktrace:\n" .
241  } else {
242  return self::getShowBacktraceError( $e ) . "\n";
243  }
244  }
245 
250  private static function getShowBacktraceError( $e ) {
251  $var = '$wgShowExceptionDetails = true;';
252  return "Set $var at the bottom of LocalSettings.php to show detailed debugging information.";
253  }
254 
258  private static function isCommandLine() {
259  return !empty( $GLOBALS['wgCommandLineMode'] );
260  }
261 
265  private static function header( $header ) {
266  if ( !headers_sent() ) {
267  header( $header );
268  }
269  }
270 
274  private static function statusHeader( $code ) {
275  if ( !headers_sent() ) {
276  HttpStatus::header( $code );
277  }
278  }
279 
287  private static function printError( $message ) {
288  // NOTE: STDERR may not be available, especially if php-cgi is used from the
289  // command line (T17602). Try to produce meaningful output anyway. Using
290  // echo may corrupt output to STDOUT though.
291  if ( defined( 'STDERR' ) ) {
292  fwrite( STDERR, $message );
293  } else {
294  echo $message;
295  }
296  }
297 
301  private static function reportOutageHTML( $e ) {
303 
304  $sorry = htmlspecialchars( self::msg(
305  'dberr-problems',
306  'Sorry! This site is experiencing technical difficulties.'
307  ) );
308  $again = htmlspecialchars( self::msg(
309  'dberr-again',
310  'Try waiting a few minutes and reloading.'
311  ) );
312 
313  if ( $wgShowHostnames ) {
314  $info = str_replace(
315  '$1',
316  Html::element( 'span', [ 'dir' => 'ltr' ], $e->getMessage() ),
317  htmlspecialchars( self::msg( 'dberr-info', '($1)' ) )
318  );
319  } else {
320  $info = htmlspecialchars( self::msg(
321  'dberr-info-hidden',
322  '(Cannot access the database)'
323  ) );
324  }
325 
326  MessageCache::singleton()->disable(); // no DB access
327  $html = "<!DOCTYPE html>\n" .
328  '<html><head>' .
329  '<title>' .
330  htmlspecialchars( $wgSitename ) .
331  '</title>' .
332  '<style>body { font-family: sans-serif; margin: 0; padding: 0.5em 2em; }</style>' .
333  "</head><body><h1>$sorry</h1><p>$again</p><p><small>$info</small></p>";
334 
335  if ( $wgShowExceptionDetails ) {
336  $html .= '<p>Backtrace:</p><pre>' .
337  htmlspecialchars( $e->getTraceAsString() ) . '</pre>';
338  }
339 
340  $html .= '</body></html>';
341  echo $html;
342  }
343 }
static getLogMessage( $e)
Get a message formatting the exception message and its origin.
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:231
static getRequestId()
Get the unique request ID.
Definition: WebRequest.php:309
$wgSitename
Name of the site.
$wgMimeType
The default Content-Type header.
Class to expose exceptions to the client (API bots, users, admins using CLI scripts) ...
$wgShowExceptionDetails
If set to true, uncaught exceptions will print the exception message and a complete stack trace to ou...
static getMain()
Get the RequestContext object associated with the main request.
static getRedactedTraceAsString( $e)
Generate a string representation of an exception&#39;s stack trace.
wfMsgReplaceArgs( $message, $args)
Replace message parameter keys on the given formatted output.
static header( $code)
Output an HTTP status code header.
Definition: HttpStatus.php:96
wfHttpError( $code, $label, $desc)
Provide a simple HTTP error.
static getURL()
If the exception occurred in the course of responding to a request, returns the requested URL...
$GLOBALS['IP']
$header
static output( $e, $mode, $eNew=null)
$fallback
Definition: MessagesAb.php:11
static getHTML( $e)
If $wgShowExceptionDetails is true, return a HTML message with a backtrace to the error...
static newFromSpecifier( $value)
Transform a MessageSpecifier or a primitive value used interchangeably with specifiers (a message key...
Definition: Message.php:430
static reportHTML( $e)
Output the exception report using HTML.
static msg( $key, $fallback,... $params)
Get a message from i18n.
$wgOut
Definition: Setup.php:863
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
static getPublicLogMessage( $e)
static singleton()
Get the singleton instance of this class.
Base class for the more common types of database errors.
static printError( $message)
Print a message, if possible to STDERR.
$wgShowHostnames
Expose backend server host names through the API and various HTML comments.