MediaWiki  1.34.0
MWExceptionRenderer.php
Go to the documentation of this file.
1 <?php
24 
30  const AS_RAW = 1; // show as text
31  const AS_PRETTY = 2; // show as HTML
32 
38  public static function output( $e, $mode, $eNew = null ) {
40 
41  if ( defined( 'MW_API' ) ) {
42  // Unhandled API exception, we can't be sure that format printer is alive
43  self::header( 'MediaWiki-API-Error: internal_api_error_' . get_class( $e ) );
44  wfHttpError( 500, 'Internal Server Error', self::getText( $e ) );
45  } elseif ( self::isCommandLine() ) {
46  self::printError( self::getText( $e ) );
47  } elseif ( $mode === self::AS_PRETTY ) {
48  self::statusHeader( 500 );
49  self::header( "Content-Type: $wgMimeType; charset=utf-8" );
50  if ( $e instanceof DBConnectionError ) {
52  } else {
53  self::reportHTML( $e );
54  }
55  } else {
56  self::statusHeader( 500 );
57  self::header( "Content-Type: $wgMimeType; charset=utf-8" );
58  if ( $eNew ) {
59  $message = "MediaWiki internal error.\n\n";
61  $message .= 'Original exception: ' .
63  "\nBacktrace:\n" . MWExceptionHandler::getRedactedTraceAsString( $e ) .
64  "\n\nException caught inside exception handler: " .
66  "\nBacktrace:\n" . MWExceptionHandler::getRedactedTraceAsString( $eNew );
67  } else {
68  $message .= 'Original exception: ' .
70  $message .= "\n\nException caught inside exception handler.\n\n" .
72  }
73  $message .= "\n";
74  } elseif ( $wgShowExceptionDetails ) {
75  $message = MWExceptionHandler::getLogMessage( $e ) .
76  "\nBacktrace:\n" .
78  } else {
80  }
81  echo nl2br( htmlspecialchars( $message ) ) . "\n";
82  }
83  }
84 
89  private static function useOutputPage( $e ) {
90  // Can the extension use the Message class/wfMessage to get i18n-ed messages?
91  foreach ( $e->getTrace() as $frame ) {
92  if ( isset( $frame['class'] ) && $frame['class'] === LocalisationCache::class ) {
93  return false;
94  }
95  }
96 
97  // Don't even bother with OutputPage if there's no Title context set,
98  // (e.g. we're in RL code on load.php) - the Skin system (and probably
99  // most of MediaWiki) won't work.
100 
101  return (
102  !empty( $GLOBALS['wgFullyInitialised'] ) &&
103  !empty( $GLOBALS['wgOut'] ) &&
104  RequestContext::getMain()->getTitle() &&
105  !defined( 'MEDIAWIKI_INSTALL' )
106  );
107  }
108 
114  private static function reportHTML( $e ) {
115  global $wgOut, $wgSitename;
116 
117  if ( self::useOutputPage( $e ) ) {
118  if ( $e instanceof MWException ) {
119  $wgOut->prepareErrorPage( $e->getPageTitle() );
120  } elseif ( $e instanceof DBReadOnlyError ) {
121  $wgOut->prepareErrorPage( self::msg( 'readonly', 'Database is locked' ) );
122  } elseif ( $e instanceof DBExpectedError ) {
123  $wgOut->prepareErrorPage( self::msg( 'databaseerror', 'Database error' ) );
124  } else {
125  $wgOut->prepareErrorPage( self::msg( 'internalerror', 'Internal error' ) );
126  }
127 
128  // Show any custom GUI message before the details
129  if ( $e instanceof MessageSpecifier ) {
130  $wgOut->addHTML( Html::element( 'p', [], Message::newFromSpecifier( $e )->text() ) );
131  }
132  $wgOut->addHTML( self::getHTML( $e ) );
133 
134  $wgOut->output();
135  } else {
136  self::header( 'Content-Type: text/html; charset=utf-8' );
137  $pageTitle = self::msg( 'internalerror', 'Internal error' );
138  echo "<!DOCTYPE html>\n" .
139  '<html><head>' .
140  // Mimick OutputPage::setPageTitle behaviour
141  '<title>' .
142  htmlspecialchars( self::msg( 'pagetitle', "$1 - $wgSitename", $pageTitle ) ) .
143  '</title>' .
144  '<style>body { font-family: sans-serif; margin: 0; padding: 0.5em 2em; }</style>' .
145  "</head><body>\n";
146 
147  echo self::getHTML( $e );
148 
149  echo "</body></html>\n";
150  }
151  }
152 
161  public static function getHTML( $e ) {
163 
164  if ( $wgShowExceptionDetails ) {
165  $html = "<div class=\"errorbox mw-content-ltr\"><p>" .
166  nl2br( htmlspecialchars( MWExceptionHandler::getLogMessage( $e ) ) ) .
167  '</p><p>Backtrace:</p><p>' .
168  nl2br( htmlspecialchars( MWExceptionHandler::getRedactedTraceAsString( $e ) ) ) .
169  "</p></div>\n";
170  } else {
171  $logId = WebRequest::getRequestId();
172  $html = "<div class=\"errorbox mw-content-ltr\">" .
173  htmlspecialchars(
174  '[' . $logId . '] ' .
175  gmdate( 'Y-m-d H:i:s' ) . ": " .
176  self::msg( "internalerror-fatal-exception",
177  "Fatal exception of type $1",
178  get_class( $e ),
179  $logId,
181  ) ) . "</div>\n" .
182  "<!-- " . wordwrap( self::getShowBacktraceError( $e ), 50 ) . " -->";
183  }
184 
185  return $html;
186  }
187 
197  private static function msg( $key, $fallback, ...$params ) {
198  global $wgSitename;
199 
200  // FIXME: Keep logic in sync with MWException::msg.
201  try {
202  $res = wfMessage( $key, ...$params )->text();
203  } catch ( Exception $e ) {
204  $res = wfMsgReplaceArgs( $fallback, $params );
205  // If an exception happens inside message rendering,
206  // {{SITENAME}} sometimes won't be replaced.
207  $res = strtr( $res, [
208  '{{SITENAME}}' => $wgSitename,
209  ] );
210  }
211  return $res;
212  }
213 
218  private static function getText( $e ) {
220 
221  if ( $wgShowExceptionDetails ) {
222  return MWExceptionHandler::getLogMessage( $e ) .
223  "\nBacktrace:\n" .
225  } else {
226  return self::getShowBacktraceError( $e ) . "\n";
227  }
228  }
229 
234  private static function getShowBacktraceError( $e ) {
235  $var = '$wgShowExceptionDetails = true;';
236  return "Set $var at the bottom of LocalSettings.php to show detailed debugging information.";
237  }
238 
242  private static function isCommandLine() {
243  return !empty( $GLOBALS['wgCommandLineMode'] );
244  }
245 
249  private static function header( $header ) {
250  if ( !headers_sent() ) {
251  header( $header );
252  }
253  }
254 
258  private static function statusHeader( $code ) {
259  if ( !headers_sent() ) {
260  HttpStatus::header( $code );
261  }
262  }
263 
271  private static function printError( $message ) {
272  // NOTE: STDERR may not be available, especially if php-cgi is used from the
273  // command line (T17602). Try to produce meaningful output anyway. Using
274  // echo may corrupt output to STDOUT though.
275  if ( defined( 'STDERR' ) ) {
276  fwrite( STDERR, $message );
277  } else {
278  echo $message;
279  }
280  }
281 
285  private static function reportOutageHTML( $e ) {
287 
288  $sorry = htmlspecialchars( self::msg(
289  'dberr-problems',
290  'Sorry! This site is experiencing technical difficulties.'
291  ) );
292  $again = htmlspecialchars( self::msg(
293  'dberr-again',
294  'Try waiting a few minutes and reloading.'
295  ) );
296 
297  if ( $wgShowHostnames ) {
298  $info = str_replace(
299  '$1',
300  Html::element( 'span', [ 'dir' => 'ltr' ], $e->getMessage() ),
301  htmlspecialchars( self::msg( 'dberr-info', '($1)' ) )
302  );
303  } else {
304  $info = htmlspecialchars( self::msg(
305  'dberr-info-hidden',
306  '(Cannot access the database)'
307  ) );
308  }
309 
310  MessageCache::singleton()->disable(); // no DB access
311  $html = "<!DOCTYPE html>\n" .
312  '<html><head>' .
313  '<title>' .
314  htmlspecialchars( $wgSitename ) .
315  '</title>' .
316  '<style>body { font-family: sans-serif; margin: 0; padding: 0.5em 2em; }</style>' .
317  "</head><body><h1>$sorry</h1><p>$again</p><p><small>$info</small></p>";
318 
319  if ( $wgShowExceptionDetails ) {
320  $html .= '<p>Backtrace:</p><pre>' .
321  htmlspecialchars( $e->getTraceAsString() ) . '</pre>';
322  }
323 
324  $html .= '</body></html>';
325  echo $html;
326  }
327 }
$wgMimeType
$wgMimeType
The default Content-Type header.
Definition: DefaultSettings.php:3251
MWExceptionRenderer\msg
static msg( $key, $fallback,... $params)
Get a message from i18n.
Definition: MWExceptionRenderer.php:197
$fallback
$fallback
Definition: MessagesAb.php:11
MessageSpecifier
Definition: MessageSpecifier.php:21
$wgShowHostnames
$wgShowHostnames
Expose backend server host names through the API and various HTML comments.
Definition: DefaultSettings.php:6338
wfMsgReplaceArgs
wfMsgReplaceArgs( $message, $args)
Replace message parameter keys on the given formatted output.
Definition: GlobalFunctions.php:1299
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1264
$res
$res
Definition: testCompression.php:52
MWExceptionRenderer\reportOutageHTML
static reportOutageHTML( $e)
Definition: MWExceptionRenderer.php:285
MWExceptionHandler\getPublicLogMessage
static getPublicLogMessage( $e)
Definition: MWExceptionHandler.php:543
MWException
MediaWiki exception.
Definition: MWException.php:26
MWExceptionHandler\getURL
static getURL()
If the exception occurred in the course of responding to a request, returns the requested URL.
Definition: MWExceptionHandler.php:495
MWExceptionHandler\getLogMessage
static getLogMessage( $e)
Get a message formatting the exception message and its origin.
Definition: MWExceptionHandler.php:510
MWExceptionRenderer\isCommandLine
static isCommandLine()
Definition: MWExceptionRenderer.php:242
MWExceptionRenderer\AS_PRETTY
const AS_PRETTY
Definition: MWExceptionRenderer.php:31
Wikimedia\Rdbms\DBReadOnlyError
Definition: DBReadOnlyError.php:27
MessageCache\singleton
static singleton()
Get the singleton instance of this class.
Definition: MessageCache.php:114
$header
$header
Definition: updateCredits.php:41
MWExceptionRenderer\useOutputPage
static useOutputPage( $e)
Definition: MWExceptionRenderer.php:89
MWExceptionRenderer\header
static header( $header)
Definition: MWExceptionRenderer.php:249
$wgSitename
$wgSitename
Name of the site.
Definition: DefaultSettings.php:80
MWExceptionRenderer\printError
static printError( $message)
Print a message, if possible to STDERR.
Definition: MWExceptionRenderer.php:271
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:431
MWExceptionRenderer\output
static output( $e, $mode, $eNew=null)
Definition: MWExceptionRenderer.php:38
wfHttpError
wfHttpError( $code, $label, $desc)
Provide a simple HTTP error.
Definition: GlobalFunctions.php:1659
HttpStatus\header
static header( $code)
Output an HTTP status code header.
Definition: HttpStatus.php:96
Wikimedia\Rdbms\DBExpectedError
Base class for the more common types of database errors.
Definition: DBExpectedError.php:32
WebRequest\getRequestId
static getRequestId()
Get the unique request ID.
Definition: WebRequest.php:303
$wgShowExceptionDetails
$wgShowExceptionDetails
If set to true, uncaught exceptions will print the exception message and a complete stack trace to ou...
Definition: DefaultSettings.php:6310
MWExceptionRenderer\statusHeader
static statusHeader( $code)
Definition: MWExceptionRenderer.php:258
Wikimedia\Rdbms\DBConnectionError
Definition: DBConnectionError.php:26
MWExceptionRenderer\getHTML
static getHTML( $e)
If $wgShowExceptionDetails is true, return a HTML message with a backtrace to the error,...
Definition: MWExceptionRenderer.php:161
MWExceptionRenderer\reportHTML
static reportHTML( $e)
Output the exception report using HTML.
Definition: MWExceptionRenderer.php:114
$wgOut
$wgOut
Definition: Setup.php:886
MWExceptionRenderer
Class to expose exceptions to the client (API bots, users, admins using CLI scripts)
Definition: MWExceptionRenderer.php:29
MWExceptionHandler\getRedactedTraceAsString
static getRedactedTraceAsString( $e)
Generate a string representation of an exception's stack trace.
Definition: MWExceptionHandler.php:404
$GLOBALS
$GLOBALS['IP']
Definition: ComposerHookHandler.php:6
MWExceptionRenderer\getText
static getText( $e)
Definition: MWExceptionRenderer.php:218
MWExceptionRenderer\getShowBacktraceError
static getShowBacktraceError( $e)
Definition: MWExceptionRenderer.php:234
MWExceptionRenderer\AS_RAW
const AS_RAW
Definition: MWExceptionRenderer.php:30