'MWDocGen' => __DIR__ . '/maintenance/mwdocgen.php',
'MWException' => __DIR__ . '/includes/exception/MWException.php',
'MWExceptionHandler' => __DIR__ . '/includes/exception/MWExceptionHandler.php',
+ 'MWExceptionRenderer' => __DIR__ . '/includes/exception/MWExceptionRenderer.php',
'MWGrants' => __DIR__ . '/includes/utils/MWGrants.php',
'MWHttpRequest' => __DIR__ . '/includes/HttpFunctions.php',
'MWMemcached' => __DIR__ . '/includes/compat/MemcachedClientCompat.php',
* Database error base class
* @ingroup Database
*/
-class DBError extends MWException {
- /** @var DatabaseBase */
+class DBError extends Exception {
+ /** @var IDatabase */
public $db;
/**
* Construct a database error
- * @param DatabaseBase $db Object which threw the error
+ * @param IDatabase $db Object which threw the error
* @param string $error A simple error message to be used for debugging
*/
- function __construct( DatabaseBase $db = null, $error ) {
+ function __construct( IDatabase $db = null, $error ) {
$this->db = $db;
parent::__construct( $error );
}
* @since 1.23
*/
class DBExpectedError extends DBError {
- /**
- * @return string
- */
- function getText() {
- global $wgShowDBErrorBacktrace;
-
- $s = $this->getTextContent() . "\n";
-
- if ( $wgShowDBErrorBacktrace ) {
- $s .= "Backtrace:\n" . $this->getTraceAsString() . "\n";
- }
-
- return $s;
- }
-
- /**
- * @return string
- */
- function getHTML() {
- global $wgShowDBErrorBacktrace;
-
- $s = $this->getHTMLContent();
-
- if ( $wgShowDBErrorBacktrace ) {
- $s .= '<p>Backtrace:</p><pre>' . htmlspecialchars( $this->getTraceAsString() ) . '</pre>';
- }
-
- return $s;
- }
-
- function getPageTitle() {
- return $this->msg( 'databaseerror', 'Database error' );
- }
-
- /**
- * @return string
- */
- protected function getTextContent() {
- return $this->getMessage();
- }
-
- /**
- * @return string
- */
- protected function getHTMLContent() {
- return '<p>' . nl2br( htmlspecialchars( $this->getTextContent() ) ) . '</p>';
- }
}
/**
* @ingroup Database
*/
class DBConnectionError extends DBExpectedError {
- /** @var string Error text */
- public $error;
-
/**
- * @param DatabaseBase $db Object throwing the error
+ * @param IDatabase $db Object throwing the error
* @param string $error Error text
*/
- function __construct( DatabaseBase $db = null, $error = 'unknown error' ) {
- $msg = 'DB connection error';
-
+ function __construct( IDatabase $db = null, $error = 'unknown error' ) {
+ $msg = 'Cannot access the database';
if ( trim( $error ) != '' ) {
$msg .= ": $error";
- } elseif ( $db ) {
- $error = $this->db->getServer();
}
parent::__construct( $db, $msg );
- $this->error = $error;
- }
-
- /**
- * @return bool
- */
- function useOutputPage() {
- // Not likely to work
- return false;
- }
-
- /**
- * @param string $key
- * @param string $fallback Unescaped alternative error text in case the
- * message cache cannot be used. Can contain parameters as in regular
- * messages, that should be passed as additional parameters.
- * @return string Unprocessed plain error text with parameters replaced
- */
- function msg( $key, $fallback /*[, params...] */ ) {
- $args = array_slice( func_get_args(), 2 );
-
- if ( $this->useMessageCache() ) {
- return wfMessage( $key, $args )->useDatabase( false )->text();
- } else {
- return wfMsgReplaceArgs( $fallback, $args );
- }
- }
-
- /**
- * @return bool
- */
- function isLoggable() {
- // Don't send to the exception log, already in dberror log
- return false;
- }
-
- /**
- * @return string Safe HTML
- */
- function getHTML() {
- global $wgShowDBErrorBacktrace, $wgShowHostnames, $wgShowSQLErrors;
-
- $sorry = htmlspecialchars( $this->msg(
- 'dberr-problems',
- 'Sorry! This site is experiencing technical difficulties.'
- ) );
- $again = htmlspecialchars( $this->msg(
- 'dberr-again',
- 'Try waiting a few minutes and reloading.'
- ) );
-
- if ( $wgShowHostnames || $wgShowSQLErrors ) {
- $info = str_replace(
- '$1', Html::element( 'span', [ 'dir' => 'ltr' ], $this->error ),
- htmlspecialchars( $this->msg( 'dberr-info', '(Cannot access the database: $1)' ) )
- );
- } else {
- $info = htmlspecialchars( $this->msg(
- 'dberr-info-hidden',
- '(Cannot access the database)'
- ) );
- }
-
- # No database access
- MessageCache::singleton()->disable();
-
- $html = "<h1>$sorry</h1><p>$again</p><p><small>$info</small></p>";
-
- if ( $wgShowDBErrorBacktrace ) {
- $html .= '<p>Backtrace:</p><pre>' . htmlspecialchars( $this->getTraceAsString() ) . '</pre>';
- }
-
- $html .= '<hr />';
- $html .= $this->searchForm();
-
- return $html;
- }
-
- protected function getTextContent() {
- global $wgShowHostnames, $wgShowSQLErrors;
-
- if ( $wgShowHostnames || $wgShowSQLErrors ) {
- return $this->getMessage();
- } else {
- return 'DB connection error';
- }
- }
-
- /**
- * Output the exception report using HTML.
- *
- * @return void
- */
- public function reportHTML() {
- global $wgUseFileCache;
-
- // Check whether we can serve a file-cached copy of the page with the error underneath
- if ( $wgUseFileCache ) {
- try {
- $cache = $this->fileCachedPage();
- // Cached version on file system?
- if ( $cache !== null ) {
- // Hack: extend the body for error messages
- $cache = str_replace( [ '</html>', '</body>' ], '', $cache );
- // Add cache notice...
- $cache .= '<div style="border:1px solid #ffd0d0;padding:1em;">' .
- htmlspecialchars( $this->msg( 'dberr-cachederror',
- 'This is a cached copy of the requested page, and may not be up to date.' ) ) .
- '</div>';
-
- // Output cached page with notices on bottom and re-close body
- echo "{$cache}<hr />{$this->getHTML()}</body></html>";
-
- return;
- }
- } catch ( Exception $e ) {
- // Do nothing, just use the default page
- }
- }
-
- // We can't, cough and die in the usual fashion
- parent::reportHTML();
- }
-
- /**
- * @return string
- */
- function searchForm() {
- global $wgSitename, $wgCanonicalServer, $wgRequest;
-
- $usegoogle = htmlspecialchars( $this->msg(
- 'dberr-usegoogle',
- 'You can try searching via Google in the meantime.'
- ) );
- $outofdate = htmlspecialchars( $this->msg(
- 'dberr-outofdate',
- 'Note that their indexes of our content may be out of date.'
- ) );
- $googlesearch = htmlspecialchars( $this->msg( 'searchbutton', 'Search' ) );
-
- $search = htmlspecialchars( $wgRequest->getVal( 'search' ) );
-
- $server = htmlspecialchars( $wgCanonicalServer );
- $sitename = htmlspecialchars( $wgSitename );
-
- $trygoogle = <<<EOT
-<div style="margin: 1.5em">$usegoogle<br />
-<small>$outofdate</small>
-</div>
-<form method="get" action="//www.google.com/search" id="googlesearch">
- <input type="hidden" name="domains" value="$server" />
- <input type="hidden" name="num" value="50" />
- <input type="hidden" name="ie" value="UTF-8" />
- <input type="hidden" name="oe" value="UTF-8" />
-
- <input type="text" name="q" size="31" maxlength="255" value="$search" />
- <input type="submit" name="btnG" value="$googlesearch" />
- <p>
- <label><input type="radio" name="sitesearch" value="$server" checked="checked" />$sitename</label>
- <label><input type="radio" name="sitesearch" value="" />WWW</label>
- </p>
-</form>
-EOT;
-
- return $trygoogle;
- }
-
- /**
- * @return string
- */
- private function fileCachedPage() {
- $context = RequestContext::getMain();
-
- if ( $context->getOutput()->isDisabled() ) {
- // Done already?
- return '';
- }
-
- if ( $context->getTitle() ) {
- // Use the main context's title if we managed to set it
- $t = $context->getTitle()->getPrefixedDBkey();
- } else {
- // Fallback to the raw title URL param. We can't use the Title
- // class is it may hit the interwiki table and give a DB error.
- // We may get a cache miss due to not sanitizing the title though.
- $t = str_replace( ' ', '_', $context->getRequest()->getVal( 'title' ) );
- if ( $t == '' ) { // fallback to main page
- $t = Title::newFromText(
- $this->msg( 'mainpage', 'Main Page' ) )->getPrefixedDBkey();
- }
- }
-
- $cache = new HTMLFileCache( $t, 'view' );
- if ( $cache->isCached() ) {
- return $cache->fetchText();
- } else {
- return '';
- }
}
}
* @ingroup Database
*/
class DBQueryError extends DBExpectedError {
- public $error, $errno, $sql, $fname;
+ /** @var string */
+ public $error;
+ /** @var integer */
+ public $errno;
+ /** @var string */
+ public $sql;
+ /** @var string */
+ public $fname;
/**
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @param string $error
* @param int|string $errno
* @param string $sql
* @param string $fname
*/
- function __construct( DatabaseBase $db, $error, $errno, $sql, $fname ) {
- if ( $db->wasConnectionError( $errno ) ) {
+ function __construct( IDatabase $db, $error, $errno, $sql, $fname ) {
+ if ( $db instanceof DatabaseBase && $db->wasConnectionError( $errno ) ) {
$message = "A connection error occured. \n" .
"Query: $sql\n" .
"Function: $fname\n" .
$this->sql = $sql;
$this->fname = $fname;
}
+}
- /**
- * @return string
- */
- function getPageTitle() {
- return $this->msg( 'databaseerror', 'Database error' );
- }
-
- /**
- * @return string
- */
- protected function getHTMLContent() {
- $key = 'databaseerror-text';
- $s = Html::element( 'p', [], $this->msg( $key, $this->getFallbackMessage( $key ) ) );
-
- $details = $this->getTechnicalDetails();
- if ( $details ) {
- $s .= '<ul>';
- foreach ( $details as $key => $detail ) {
- $s .= str_replace(
- '$1', call_user_func_array( 'Html::element', $detail ),
- Html::element( 'li', [],
- $this->msg( $key, $this->getFallbackMessage( $key ) )
- )
- );
- }
- $s .= '</ul>';
- }
-
- return $s;
- }
-
- /**
- * @return string
- */
- protected function getTextContent() {
- $key = 'databaseerror-textcl';
- $s = $this->msg( $key, $this->getFallbackMessage( $key ) ) . "\n";
-
- foreach ( $this->getTechnicalDetails() as $key => $detail ) {
- $s .= $this->msg( $key, $this->getFallbackMessage( $key ), $detail[2] ) . "\n";
- }
-
- return $s;
- }
-
- /**
- * Make a list of technical details that can be shown to the user. This information can
- * aid in debugging yet may be useful to an attacker trying to exploit a security weakness
- * in the software or server configuration.
- *
- * Thus no such details are shown by default, though if $wgShowHostnames is true, only the
- * full SQL query is hidden; in fact, the error message often does contain a hostname, and
- * sites using this option probably don't care much about "security by obscurity". Of course,
- * if $wgShowSQLErrors is true, the SQL query *is* shown.
- *
- * @return array Keys are message keys; values are arrays of arguments for Html::element().
- * Array will be empty if users are not allowed to see any of these details at all.
- */
- protected function getTechnicalDetails() {
- global $wgShowHostnames, $wgShowSQLErrors;
-
- $attribs = [ 'dir' => 'ltr' ];
- $details = [];
-
- if ( $wgShowSQLErrors ) {
- $details['databaseerror-query'] = [
- 'div', [ 'class' => 'mw-code' ] + $attribs, $this->sql ];
- }
-
- if ( $wgShowHostnames || $wgShowSQLErrors ) {
- $errorMessage = $this->errno . ' ' . $this->error;
- $details['databaseerror-function'] = [ 'code', $attribs, $this->fname ];
- $details['databaseerror-error'] = [ 'samp', $attribs, $errorMessage ];
- }
-
- return $details;
- }
-
- /**
- * @param string $key Message key
- * @return string English message text
- */
- private function getFallbackMessage( $key ) {
- $messages = [
- 'databaseerror-text' => 'A database query error has occurred.
-This may indicate a bug in the software.',
- 'databaseerror-textcl' => 'A database query error has occurred.',
- 'databaseerror-query' => 'Query: $1',
- 'databaseerror-function' => 'Function: $1',
- 'databaseerror-error' => 'Error: $1',
- ];
-
- return $messages[$key];
- }
+/**
+ * @ingroup Database
+ */
+class DBReadOnlyError extends DBExpectedError {
}
/**
* @ingroup Database
*/
-class DBUnexpectedError extends DBError {
+class DBTransactionError extends DBExpectedError {
}
/**
+ * Exception class for replica DB wait timeouts
* @ingroup Database
*/
-class DBReadOnlyError extends DBExpectedError {
- function getPageTitle() {
- return $this->msg( 'readonly', 'Database is locked' );
- }
+class DBReplicationWaitError extends DBExpectedError {
}
/**
* @ingroup Database
*/
-class DBTransactionError extends DBExpectedError {
+class DBUnexpectedError extends DBError {
}
/**
}
}
-/**
- * Exception class for replica DB wait timeouts
- * @ingroup Database
- */
-class DBReplicationWaitError extends DBUnexpectedError {
-}
/**
* Basic database interface for live and lazy-loaded DB handles
*
- * @todo: loosen up DB classes from MWException
* @note: IDatabase and DBConnRef should be updated to reflect any changes
* @ingroup Database
*/
/**
* Interface for database load balancing object that manages IDatabase handles
*
- * @todo: loosen up DB classes from MWException
* @since 1.28
* @ingroup Database
*/
* @return string|null String to output or null if any hook has been called
*/
public function runHooks( $name, $args = [] ) {
- global $wgExceptionHooks;
-
- if ( !isset( $wgExceptionHooks ) || !is_array( $wgExceptionHooks ) ) {
- return null; // Just silently ignore
- }
-
- if ( !array_key_exists( $name, $wgExceptionHooks ) ||
- !is_array( $wgExceptionHooks[$name] )
- ) {
- return null;
- }
-
- $hooks = $wgExceptionHooks[$name];
- $callargs = array_merge( [ $this ], $args );
-
- foreach ( $hooks as $hook ) {
- if (
- is_string( $hook ) ||
- ( is_array( $hook ) && count( $hook ) >= 2 && is_string( $hook[0] ) )
- ) {
- // 'function' or [ 'class', 'hook' ]
- $result = call_user_func_array( $hook, $callargs );
- } else {
- $result = null;
- }
-
- if ( is_string( $result ) ) {
- return $result;
- }
- }
- return null;
+ return MWExceptionRenderer::runHooks( $this, $name, $args );
}
/**
* It will be either HTML or plain text based on isCommandLine().
*/
public function report() {
- global $wgMimeType;
-
- if ( defined( 'MW_API' ) ) {
- // Unhandled API exception, we can't be sure that format printer is alive
- self::header( 'MediaWiki-API-Error: internal_api_error_' . get_class( $this ) );
- wfHttpError( 500, 'Internal Server Error', $this->getText() );
- } elseif ( self::isCommandLine() ) {
- MWExceptionHandler::printError( $this->getText() );
- } else {
- self::statusHeader( 500 );
- self::header( "Content-Type: $wgMimeType; charset=utf-8" );
-
- $this->reportHTML();
- }
+ MWExceptionRenderer::output( $this, MWExceptionRenderer::AS_PRETTY );
}
/**
* @param Exception|Throwable $e
*/
protected static function report( $e ) {
- global $wgShowExceptionDetails;
-
- $cmdLine = MWException::isCommandLine();
-
- if ( $e instanceof MWException ) {
- try {
- // Try and show the exception prettily, with the normal skin infrastructure
- $e->report();
- } catch ( Exception $e2 ) {
- // Exception occurred from within exception handler
- // Show a simpler message for the original exception,
- // don't try to invoke report()
- $message = "MediaWiki internal error.\n\n";
-
- if ( $wgShowExceptionDetails ) {
- $message .= 'Original exception: ' . self::getLogMessage( $e ) .
- "\nBacktrace:\n" . self::getRedactedTraceAsString( $e ) .
- "\n\nException caught inside exception handler: " . self::getLogMessage( $e2 ) .
- "\nBacktrace:\n" . self::getRedactedTraceAsString( $e2 );
- } else {
- $message .= "Exception caught inside exception handler.\n\n" .
- "Set \$wgShowExceptionDetails = true; at the bottom of LocalSettings.php " .
- "to show detailed debugging information.";
- }
-
- $message .= "\n";
-
- if ( $cmdLine ) {
- self::printError( $message );
- } else {
- echo nl2br( htmlspecialchars( $message ) ) . "\n";
- }
- }
- } else {
- if ( !$wgShowExceptionDetails ) {
- $message = self::getPublicLogMessage( $e );
- } else {
- $message = self::getLogMessage( $e ) .
- "\nBacktrace:\n" .
- self::getRedactedTraceAsString( $e ) . "\n";
- }
-
- if ( $cmdLine ) {
- self::printError( $message );
- } else {
- echo nl2br( htmlspecialchars( $message ) ) . "\n";
- }
-
- }
- }
-
- /**
- * Print a message, if possible to STDERR.
- * Use this in command line mode only (see isCommandLine)
- *
- * @param string $message Failure text
- */
- public static function printError( $message ) {
- # NOTE: STDERR may not be available, especially if php-cgi is used from the
- # command line (bug #15602). Try to produce meaningful output anyway. Using
- # echo may corrupt output to STDOUT though.
- if ( defined( 'STDERR' ) ) {
- fwrite( STDERR, $message );
- } else {
- echo $message;
+ try {
+ // Try and show the exception prettily, with the normal skin infrastructure
+ MWExceptionRenderer::output( $e, MWExceptionRenderer::AS_PRETTY );
+ } catch ( Exception $e2 ) {
+ // Exception occurred from within exception handler
+ // Show a simpler message for the original exception,
+ // don't try to invoke report()
+ MWExceptionRenderer::output( $e, MWExceptionRenderer::AS_PRETTY, $e2 );
}
}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @author Aaron Schulz
+ */
+
+/**
+ * Class to expose exceptions to the client (API bots, users, admins using CLI scripts)
+ * @since 1.28
+ */
+class MWExceptionRenderer {
+ const AS_RAW = 1; // show as text
+ const AS_PRETTY = 2; // show as HTML
+
+ /**
+ * @param Exception $e Original exception
+ * @param integer $mode MWExceptionExposer::AS_* constant
+ * @param Exception|null $eNew New exception from attempting to show the first
+ */
+ public static function output( Exception $e, $mode, Exception $eNew = null ) {
+ global $wgMimeType;
+
+ if ( $e instanceof DBConnectionError ) {
+ self::reportOutageHTML( $e );
+ return;
+ }
+
+ if ( defined( 'MW_API' ) ) {
+ // Unhandled API exception, we can't be sure that format printer is alive
+ self::header( 'MediaWiki-API-Error: internal_api_error_' . get_class( $e ) );
+ wfHttpError( 500, 'Internal Server Error', self::getText( $e ) );
+ } elseif ( self::isCommandLine() ) {
+ self::printError( self::getText( $e ) );
+ } elseif ( $mode === self::AS_PRETTY ) {
+ self::statusHeader( 500 );
+ self::header( "Content-Type: $wgMimeType; charset=utf-8" );
+ self::reportHTML( $e );
+ } else {
+ if ( $eNew ) {
+ $message = "MediaWiki internal error.\n\n";
+ if ( self::showBackTrace( $e ) ) {
+ $message .= 'Original exception: ' .
+ MWExceptionHandler::getLogMessage( $e ) .
+ "\nBacktrace:\n" . MWExceptionHandler::getRedactedTraceAsString( $e ) .
+ "\n\nException caught inside exception handler: " .
+ MWExceptionHandler::getLogMessage( $eNew ) .
+ "\nBacktrace:\n" . MWExceptionHandler::getRedactedTraceAsString( $eNew );
+ } else {
+ $message .= "Exception caught inside exception handler.\n\n" .
+ "Set \$wgShowExceptionDetails = true; at the bottom of LocalSettings.php " .
+ "to show detailed debugging information.";
+ }
+ $message .= "\n";
+ } else {
+ if ( self::showBackTrace( $e ) ) {
+ $message = MWExceptionHandler::getLogMessage( $e ) .
+ "\nBacktrace:\n" .
+ MWExceptionHandler::getRedactedTraceAsString( $e ) . "\n";
+ } else {
+ $message = MWExceptionHandler::getPublicLogMessage( $e );
+ }
+ }
+ if ( self::isCommandLine() ) {
+ self::printError( $message );
+ } else {
+ echo nl2br( htmlspecialchars( $message ) ) . "\n";
+ }
+ }
+ }
+
+ /**
+ * Run hook to allow extensions to modify the text of the exception
+ *
+ * Called by MWException for b/c
+ *
+ * @param Exception $e
+ * @param string $name Class name of the exception
+ * @param array $args Arguments to pass to the callback functions
+ * @return string|null String to output or null if any hook has been called
+ */
+ public static function runHooks( Exception $e, $name, $args = [] ) {
+ global $wgExceptionHooks;
+
+ if ( !isset( $wgExceptionHooks ) || !is_array( $wgExceptionHooks ) ) {
+ return null; // Just silently ignore
+ }
+
+ if ( !array_key_exists( $name, $wgExceptionHooks ) ||
+ !is_array( $wgExceptionHooks[$name] )
+ ) {
+ return null;
+ }
+
+ $hooks = $wgExceptionHooks[$name];
+ $callargs = array_merge( [ $e ], $args );
+
+ foreach ( $hooks as $hook ) {
+ if (
+ is_string( $hook ) ||
+ ( is_array( $hook ) && count( $hook ) >= 2 && is_string( $hook[0] ) )
+ ) {
+ // 'function' or [ 'class', 'hook' ]
+ $result = call_user_func_array( $hook, $callargs );
+ } else {
+ $result = null;
+ }
+
+ if ( is_string( $result ) ) {
+ return $result;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @param Exception $e
+ * @return bool Should the exception use $wgOut to output the error?
+ */
+ private static function useOutputPage( Exception $e ) {
+ // Can the extension use the Message class/wfMessage to get i18n-ed messages?
+ $useMessageCache = ( $GLOBALS['wgLang'] instanceof Language );
+ foreach ( $e->getTrace() as $frame ) {
+ if ( isset( $frame['class'] ) && $frame['class'] === 'LocalisationCache' ) {
+ $useMessageCache = false;
+ }
+ }
+
+ return (
+ $useMessageCache &&
+ !empty( $GLOBALS['wgFullyInitialised'] ) &&
+ !empty( $GLOBALS['wgOut'] ) &&
+ !defined( 'MEDIAWIKI_INSTALL' )
+ );
+ }
+
+ /**
+ * Output the exception report using HTML
+ *
+ * @param Exception $e
+ */
+ private static function reportHTML( Exception $e ) {
+ global $wgOut, $wgSitename;
+
+ if ( self::useOutputPage( $e ) ) {
+ if ( $e instanceof MWException ) {
+ $wgOut->prepareErrorPage( $e->getPageTitle() );
+ } elseif ( $e instanceof DBReadOnlyError ) {
+ $wgOut->prepareErrorPage( self::msg( 'readonly', 'Database is locked' ) );
+ } elseif ( $e instanceof DBExpectedError ) {
+ $wgOut->prepareErrorPage( self::msg( 'databaseerror', 'Database error' ) );
+ } else {
+ $wgOut->prepareErrorPage( self::msg( 'internalerror', 'Internal error' ) );
+ }
+
+ $hookResult = self::runHooks( $e, get_class( $e ) );
+ if ( $hookResult ) {
+ $wgOut->addHTML( $hookResult );
+ } else {
+ $wgOut->addHTML( self::getHTML( $e ) );
+ }
+
+ $wgOut->output();
+ } else {
+ self::header( 'Content-Type: text/html; charset=utf-8' );
+ $pageTitle = self::msg( 'internalerror', 'Internal error' );
+ echo "<!DOCTYPE html>\n" .
+ '<html><head>' .
+ // Mimick OutputPage::setPageTitle behaviour
+ '<title>' .
+ htmlspecialchars( self::msg( 'pagetitle', "$1 - $wgSitename", $pageTitle ) ) .
+ '</title>' .
+ '<style>body { font-family: sans-serif; margin: 0; padding: 0.5em 2em; }</style>' .
+ "</head><body>\n";
+
+ $hookResult = self::runHooks( $e, get_class( $e ) . 'Raw' );
+ if ( $hookResult ) {
+ echo $hookResult;
+ } else {
+ echo self::getHTML( $e );
+ }
+
+ echo "</body></html>\n";
+ }
+ }
+
+ /**
+ * If $wgShowExceptionDetails is true, return a HTML message with a
+ * backtrace to the error, otherwise show a message to ask to set it to true
+ * to show that information.
+ *
+ * @param Exception $e
+ * @return string Html to output
+ */
+ private static function getHTML( Exception $e ) {
+ if ( self::showBackTrace( $e ) ) {
+ return '<p>' .
+ nl2br( htmlspecialchars( MWExceptionHandler::getLogMessage( $e ) ) ) .
+ '</p><p>Backtrace:</p><p>' .
+ nl2br( htmlspecialchars( MWExceptionHandler::getRedactedTraceAsString( $e ) ) ) .
+ "</p>\n";
+ } else {
+ $logId = WebRequest::getRequestId();
+ return "<div class=\"errorbox\">" .
+ '[' . $logId . '] ' .
+ gmdate( 'Y-m-d H:i:s' ) . ": " .
+ self::msg( "internalerror-fatal-exception",
+ "Fatal exception of type $1",
+ get_class( $e ),
+ $logId,
+ MWExceptionHandler::getURL()
+ ) . "</div>\n" .
+ "<!-- Set \$wgShowExceptionDetails = true; " .
+ "at the bottom of LocalSettings.php to show detailed " .
+ "debugging information. -->";
+ }
+ }
+
+ /**
+ * Get a message from i18n
+ *
+ * @param string $key Message name
+ * @param string $fallback Default message if the message cache can't be
+ * called by the exception
+ * The function also has other parameters that are arguments for the message
+ * @return string Message with arguments replaced
+ */
+ private static function msg( $key, $fallback /*[, params...] */ ) {
+ $args = array_slice( func_get_args(), 2 );
+ try {
+ return wfMessage( $key, $args )->text();
+ } catch ( Exception $e ) {
+ return wfMsgReplaceArgs( $fallback, $args );
+ }
+ }
+
+ /**
+ * @param Exception $e
+ * @return string
+ */
+ private function getText( Exception $e ) {
+ if ( self::showBackTrace( $e ) ) {
+ return MWExceptionHandler::getLogMessage( $e ) .
+ "\nBacktrace:\n" .
+ MWExceptionHandler::getRedactedTraceAsString( $e ) . "\n";
+ } else {
+ return "Set \$wgShowExceptionDetails = true; " .
+ "in LocalSettings.php to show detailed debugging information.\n";
+ }
+ }
+
+ /**
+ * @param Exception $e
+ * @return bool
+ */
+ private static function showBackTrace( Exception $e ) {
+ global $wgShowExceptionDetails, $wgShowDBErrorBacktrace;
+
+ return (
+ $wgShowExceptionDetails &&
+ ( !( $e instanceof DBError ) || $wgShowDBErrorBacktrace )
+ );
+ }
+
+ /**
+ * @return bool
+ */
+ private static function isCommandLine() {
+ return !empty( $GLOBALS['wgCommandLineMode'] );
+ }
+
+ /**
+ * @param string $header
+ */
+ private static function header( $header ) {
+ if ( !headers_sent() ) {
+ header( $header );
+ }
+ }
+
+ /**
+ * @param integer $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)
+ *
+ * @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 (bug #15602). Try to produce meaningful output anyway. Using
+ // echo may corrupt output to STDOUT though.
+ if ( defined( 'STDERR' ) ) {
+ fwrite( STDERR, $message );
+ } else {
+ echo $message;
+ }
+ }
+
+ /**
+ * @param Exception $e
+ */
+ private static function reportOutageHTML( Exception $e ) {
+ global $wgShowDBErrorBacktrace, $wgShowHostnames, $wgShowSQLErrors;
+
+ $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 ( $wgShowHostnames || $wgShowSQLErrors ) {
+ $info = str_replace(
+ '$1',
+ Html::element( 'span', [ 'dir' => 'ltr' ], htmlspecialchars( $e->getMessage() ) ),
+ htmlspecialchars( self::msg( 'dberr-info', '($1)' ) )
+ );
+ } else {
+ $info = htmlspecialchars( self::msg(
+ 'dberr-info-hidden',
+ '(Cannot access the database)'
+ ) );
+ }
+
+ MessageCache::singleton()->disable(); // no DB access
+
+ $html = "<h1>$sorry</h1><p>$again</p><p><small>$info</small></p>";
+
+ if ( $wgShowDBErrorBacktrace ) {
+ $html .= '<p>Backtrace:</p><pre>' .
+ htmlspecialchars( $e->getTraceAsString() ) . '</pre>';
+ }
+
+ $html .= '<hr />';
+ $html .= self::googleSearchForm();
+
+ echo $html;
+ }
+
+ /**
+ * @return string
+ */
+ private static function googleSearchForm() {
+ global $wgSitename, $wgCanonicalServer, $wgRequest;
+
+ $usegoogle = htmlspecialchars( self::msg(
+ 'dberr-usegoogle',
+ 'You can try searching via Google in the meantime.'
+ ) );
+ $outofdate = htmlspecialchars( self::msg(
+ 'dberr-outofdate',
+ 'Note that their indexes of our content may be out of date.'
+ ) );
+ $googlesearch = htmlspecialchars( self::msg( 'searchbutton', 'Search' ) );
+ $search = htmlspecialchars( $wgRequest->getVal( 'search' ) );
+ $server = htmlspecialchars( $wgCanonicalServer );
+ $sitename = htmlspecialchars( $wgSitename );
+ $trygoogle = <<<EOT
+<div style="margin: 1.5em">$usegoogle<br />
+<small>$outofdate</small>
+</div>
+<form method="get" action="//www.google.com/search" id="googlesearch">
+ <input type="hidden" name="domains" value="$server" />
+ <input type="hidden" name="num" value="50" />
+ <input type="hidden" name="ie" value="UTF-8" />
+ <input type="hidden" name="oe" value="UTF-8" />
+ <input type="text" name="q" size="31" maxlength="255" value="$search" />
+ <input type="submit" name="btnG" value="$googlesearch" />
+ <p>
+ <label><input type="radio" name="sitesearch" value="$server" checked="checked" />$sitename</label>
+ <label><input type="radio" name="sitesearch" value="" />WWW</label>
+ </p>
+</form>
+EOT;
+ return $trygoogle;
+ }
+}