Reedy has uploaded a new change for review. https://gerrit.wikimedia.org/r/322707
Change subject: Revert "Add MWExceptionRenderer class and decouple DBError" ...................................................................... Revert "Add MWExceptionRenderer class and decouple DBError" This reverts commit 00bee029718f3215396e984d04b9450bc3872503. Change-Id: Idb1b6da0bae096a58db5069b0965734fae18b4f1 --- M autoload.php M includes/db/DatabaseError.php M includes/db/IDatabase.php M includes/db/loadbalancer/ILoadBalancer.php M includes/exception/MWException.php M includes/exception/MWExceptionHandler.php D includes/exception/MWExceptionRenderer.php 7 files changed, 487 insertions(+), 447 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core refs/changes/07/322707/1 diff --git a/autoload.php b/autoload.php index 5141c35..a71d943 100644 --- a/autoload.php +++ b/autoload.php @@ -767,7 +767,6 @@ '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', diff --git a/includes/db/DatabaseError.php b/includes/db/DatabaseError.php index 2242c5a..cfae74f 100644 --- a/includes/db/DatabaseError.php +++ b/includes/db/DatabaseError.php @@ -25,16 +25,16 @@ * Database error base class * @ingroup Database */ -class DBError extends Exception { - /** @var IDatabase */ +class DBError extends MWException { + /** @var DatabaseBase */ public $db; /** * Construct a database error - * @param IDatabase $db Object which threw the error + * @param DatabaseBase $db Object which threw the error * @param string $error A simple error message to be used for debugging */ - function __construct( IDatabase $db = null, $error ) { + function __construct( DatabaseBase $db = null, $error ) { $this->db = $db; parent::__construct( $error ); } @@ -48,23 +48,274 @@ * @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 IDatabase $db Object throwing the error + * @param DatabaseBase $db Object throwing the error * @param string $error Error text */ - function __construct( IDatabase $db = null, $error = 'unknown error' ) { - $msg = 'Cannot access the database'; + function __construct( DatabaseBase $db = null, $error = 'unknown error' ) { + $msg = 'DB connection error'; + 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 ''; + } } } @@ -72,24 +323,17 @@ * @ingroup Database */ class DBQueryError extends DBExpectedError { - /** @var string */ - public $error; - /** @var integer */ - public $errno; - /** @var string */ - public $sql; - /** @var string */ - public $fname; + public $error, $errno, $sql, $fname; /** - * @param IDatabase $db + * @param DatabaseBase $db * @param string $error * @param int|string $errno * @param string $sql * @param string $fname */ - function __construct( IDatabase $db, $error, $errno, $sql, $fname ) { - if ( $db instanceof DatabaseBase && $db->wasConnectionError( $errno ) ) { + function __construct( DatabaseBase $db, $error, $errno, $sql, $fname ) { + if ( $db->wasConnectionError( $errno ) ) { $message = "A connection error occured. \n" . "Query: $sql\n" . "Function: $fname\n" . @@ -109,31 +353,122 @@ $this->sql = $sql; $this->fname = $fname; } -} -/** - * @ingroup Database - */ -class DBReadOnlyError extends DBExpectedError { -} + /** + * @return string + */ + function getPageTitle() { + return $this->msg( 'databaseerror', 'Database error' ); + } -/** - * @ingroup Database - */ -class DBTransactionError extends DBExpectedError { -} + /** + * @return string + */ + protected function getHTMLContent() { + $key = 'databaseerror-text'; + $s = Html::element( 'p', [], $this->msg( $key, $this->getFallbackMessage( $key ) ) ); -/** - * Exception class for replica DB wait timeouts - * @ingroup Database - */ -class DBReplicationWaitError extends DBExpectedError { + $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 DBUnexpectedError extends DBError { +} + +/** + * @ingroup Database + */ +class DBReadOnlyError extends DBExpectedError { + function getPageTitle() { + return $this->msg( 'readonly', 'Database is locked' ); + } +} + +/** + * @ingroup Database + */ +class DBTransactionError extends DBExpectedError { } /** @@ -147,3 +482,9 @@ } } +/** + * Exception class for replica DB wait timeouts + * @ingroup Database + */ +class DBReplicationWaitError extends DBUnexpectedError { +} diff --git a/includes/db/IDatabase.php b/includes/db/IDatabase.php index e2d7436..f312357 100644 --- a/includes/db/IDatabase.php +++ b/includes/db/IDatabase.php @@ -28,6 +28,7 @@ /** * 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 */ diff --git a/includes/db/loadbalancer/ILoadBalancer.php b/includes/db/loadbalancer/ILoadBalancer.php index 94e0f2b..9313ccd 100644 --- a/includes/db/loadbalancer/ILoadBalancer.php +++ b/includes/db/loadbalancer/ILoadBalancer.php @@ -25,6 +25,7 @@ /** * Interface for database load balancing object that manages IDatabase handles * + * @todo: loosen up DB classes from MWException * @since 1.28 * @ingroup Database */ diff --git a/includes/exception/MWException.php b/includes/exception/MWException.php index 5496cb6..0a174fe 100644 --- a/includes/exception/MWException.php +++ b/includes/exception/MWException.php @@ -71,7 +71,37 @@ * @return string|null String to output or null if any hook has been called */ public function runHooks( $name, $args = [] ) { - return MWExceptionRenderer::runHooks( $this, $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; } /** @@ -199,7 +229,20 @@ * It will be either HTML or plain text based on isCommandLine(). */ public function report() { - MWExceptionRenderer::output( $this, MWExceptionRenderer::AS_PRETTY ); + 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(); + } } /** diff --git a/includes/exception/MWExceptionHandler.php b/includes/exception/MWExceptionHandler.php index 8359846..9c83d3c 100644 --- a/includes/exception/MWExceptionHandler.php +++ b/includes/exception/MWExceptionHandler.php @@ -60,14 +60,71 @@ * @param Exception|Throwable $e */ protected static function report( $e ) { - 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 ); + 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; } } diff --git a/includes/exception/MWExceptionRenderer.php b/includes/exception/MWExceptionRenderer.php deleted file mode 100644 index f455191..0000000 --- a/includes/exception/MWExceptionRenderer.php +++ /dev/null @@ -1,402 +0,0 @@ -<?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; - } -} -- To view, visit https://gerrit.wikimedia.org/r/322707 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Idb1b6da0bae096a58db5069b0965734fae18b4f1 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/core Gerrit-Branch: master Gerrit-Owner: Reedy <re...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits