getTrace() as $frame ) { if ( isset( $frame['class'] ) && $frame['class'] === LocalisationCache::class ) { return false; } } // Don't even bother with OutputPage if there's no Title context set, // (e.g. we're in RL code on load.php) - the Skin system (and probably // most of MediaWiki) won't work. return ( !empty( $GLOBALS['wgFullyInitialised'] ) && !empty( $GLOBALS['wgOut'] ) && RequestContext::getMain()->getTitle() && !defined( 'MEDIAWIKI_INSTALL' ) && // Don't send a skinned HTTP 500 page to API clients. !defined( 'MW_API' ) && !defined( 'MW_REST_API' ) ); } /** * Output the throwable report using HTML */ private static function reportHTML( Throwable $e ) { if ( self::useOutputPage( $e ) ) { $out = RequestContext::getMain()->getOutput(); $out->prepareErrorPage(); $out->addModuleStyles( 'mediawiki.codex.messagebox.styles' ); $out->setPageTitleMsg( self::getExceptionTitle( $e ) ); // Show any custom GUI message before the details $customMessage = self::getCustomMessage( $e ); if ( $customMessage !== null ) { $out->addHTML( Html::element( 'p', [], $customMessage ) ); } $out->addHTML( self::getHTML( $e ) ); // Content-Type is set by OutputPage::output $out->output(); } else { self::header( 'Content-Type: text/html; charset=UTF-8' ); $pageTitle = self::msg( 'internalerror', 'Internal error' ); echo "\n" . '' . // Mimic OutputPage::setPageTitle behaviour '' . htmlspecialchars( self::msg( 'pagetitle', '$1 - MediaWiki', $pageTitle ) ) . '' . '' . '' . "\n"; echo self::getHTML( $e ); echo "\n"; } } /** * Format an HTML message for the given exception object. * * @param Throwable $e * @return string Html to output */ public static function getHTML( Throwable $e ) { if ( self::shouldShowExceptionDetails() ) { $html = '
' . Html::errorBox( "

" . nl2br( htmlspecialchars( MWExceptionHandler::getLogMessage( $e ) ) ) . '

Backtrace:

' . nl2br( htmlspecialchars( MWExceptionHandler::getRedactedTraceAsString( $e ) ) ) . "

\n" ) . '
'; } else { $logId = WebRequest::getRequestId(); $html = Html::errorBox( htmlspecialchars( '[' . $logId . '] ' . gmdate( 'Y-m-d H:i:s' ) . ": " . self::msg( "internalerror-fatal-exception", "Fatal exception of type $1", get_class( $e ), $logId, MWExceptionHandler::getURL() ) ) ) . ""; } return $html; } /** * Get a message string from i18n * * @param string $key Message name * @param string $fallback Default message if the message cache can't be * called by the exception * @phpcs:ignore Generic.Files.LineLength * @param MessageParam|MessageSpecifier|string|int|float|list ...$params * See Message::params() * @return string Message with arguments replaced */ public static function msg( $key, $fallback, ...$params ) { // NOTE: Keep logic in sync with MWException::msg $res = self::msgObj( $key, $fallback, ...$params )->text(); return strtr( $res, [ '{{SITENAME}}' => 'MediaWiki', ] ); } /** Get a Message object from i18n. * * @param string $key Message name * @param string $fallback Default message if the message cache can't be * called by the exception * @phpcs:ignore Generic.Files.LineLength * @param MessageParam|MessageSpecifier|string|int|float|list ...$params * See Message::params() * @return Message|RawMessage */ private static function msgObj( string $key, string $fallback, ...$params ): Message { // NOTE: Keep logic in sync with MWException::msg. try { $res = wfMessage( $key, ...$params ); } catch ( Exception $e ) { // Fallback to static message text and generic sitename. // Avoid live config as this must work before Setup/MediaWikiServices finish. $res = new RawMessage( $fallback, $params ); } // We are in an error state, best to minimize how much work we do. $res->useDatabase( false ); $isSafeToLoad = RequestContext::getMain()->getUser()->isSafeToLoad(); if ( !$isSafeToLoad ) { $res->inContentLanguage(); } return $res; } /** * @param Throwable $e * @return string */ private static function getText( Throwable $e ) { // XXX: do we need a parameter to control inclusion of exception details? if ( self::shouldShowExceptionDetails() ) { return MWExceptionHandler::getLogMessage( $e ) . "\nBacktrace:\n" . MWExceptionHandler::getRedactedTraceAsString( $e ) . "\n"; } else { return self::getShowBacktraceError() . "\n"; } } /** * @return string */ private static function getShowBacktraceError() { $var = '$wgShowExceptionDetails = true;'; return "Set $var at the bottom of LocalSettings.php to show detailed debugging information."; } /** * Get the page title to be used for a given exception. * * @param Throwable $e * @return Message */ private static function getExceptionTitle( Throwable $e ): Message { if ( $e instanceof DBReadOnlyError ) { return self::msgObj( 'readonly', 'Database is locked' ); } elseif ( $e instanceof DBExpectedError ) { return self::msgObj( 'databaseerror', 'Database error' ); } elseif ( $e instanceof RequestTimeoutException ) { return self::msgObj( 'timeouterror', 'Request timeout' ); } else { return self::msgObj( 'internalerror', 'Internal error' ); } } /** * Extract an additional user-visible message from an exception, or null if * it has none. * * @param Throwable $e * @return string|null */ private static function getCustomMessage( Throwable $e ) { try { if ( $e instanceof MessageSpecifier ) { $msg = Message::newFromSpecifier( $e ); } elseif ( $e instanceof RequestTimeoutException ) { $msg = wfMessage( 'timeouterror-text', $e->getLimit() ); } else { return null; } $text = $msg->text(); } catch ( Exception $e2 ) { return null; } return $text; } /** * @return bool */ private static function isCommandLine() { return MW_ENTRY_POINT === 'cli'; } /** * @param string $header */ private static function header( $header ) { if ( !headers_sent() ) { header( $header ); } } /** * @param int $code */ private static function statusHeader( $code ) { if ( !headers_sent() ) { HttpStatus::header( $code ); } } /** * Print a message, if possible to STDERR. * Use this in command line mode only (see isCommandLine) * * @suppress SecurityCheck-XSS * @param string $message Failure text */ private static function printError( $message ) { // NOTE: STDERR may not be available, especially if php-cgi is used from the // command line (T17602). Try to produce meaningful output anyway. Using // echo may corrupt output to STDOUT though. if ( !defined( 'MW_PHPUNIT_TEST' ) && defined( 'STDERR' ) ) { fwrite( STDERR, $message ); } else { echo $message; } } private static function reportOutageHTML( Throwable $e ) { $mainConfig = MediaWikiServices::getInstance()->getMainConfig(); $showExceptionDetails = $mainConfig->get( MainConfigNames::ShowExceptionDetails ); $showHostnames = $mainConfig->get( MainConfigNames::ShowHostnames ); $sorry = htmlspecialchars( self::msg( 'dberr-problems', 'Sorry! This site is experiencing technical difficulties.' ) ); $again = htmlspecialchars( self::msg( 'dberr-again', 'Try waiting a few minutes and reloading.' ) ); if ( $showHostnames ) { $info = str_replace( '$1', Html::element( 'span', [ 'dir' => 'ltr' ], $e->getMessage() ), htmlspecialchars( self::msg( 'dberr-info', '($1)' ) ) ); } else { $info = htmlspecialchars( self::msg( 'dberr-info-hidden', '(Cannot access the database)' ) ); } MediaWikiServices::getInstance()->getMessageCache()->disable(); // no DB access $html = "\n" . '' . 'MediaWiki' . '' . '' . "

$sorry

$again

$info

"; if ( $showExceptionDetails ) { $html .= '

Backtrace:

' .
				htmlspecialchars( $e->getTraceAsString() ) . '
'; } $html .= ''; self::header( 'Content-Type: text/html; charset=UTF-8' ); echo $html; } }