Gergő Tisza has uploaded a new change for review. (
https://gerrit.wikimedia.org/r/355061 )
Change subject: Turn MWExceptionRenderer into a service
......................................................................
Turn MWExceptionRenderer into a service
Splits MWExceptionRenderer into two classes, one taking care of
relatively straightforward edge cases (command-line error etc)
and hold shared functionality, and another one with the code
that's more likely to be replaced for custom error display.
Bug: T111731
Change-Id: Ifb01b454ec2fbe5f9ecb375a12b0e7e8a884ee22
---
M autoload.php
M includes/MediaWiki.php
M includes/MediaWikiServices.php
M includes/ServiceWiring.php
M includes/exception/MWExceptionHandler.php
M includes/exception/MWExceptionRenderer.php
A includes/exception/Renderer.php
A includes/exception/StandardRenderer.php
M tests/phpunit/includes/MediaWikiServicesTest.php
9 files changed, 531 insertions(+), 318 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core
refs/changes/61/355061/1
diff --git a/autoload.php b/autoload.php
index fbdee83..8aa1533 100644
--- a/autoload.php
+++ b/autoload.php
@@ -870,6 +870,8 @@
'MediaWiki\\Auth\\UsernameAuthenticationRequest' => __DIR__ .
'/includes/auth/UsernameAuthenticationRequest.php',
'MediaWiki\\Diff\\ComplexityException' => __DIR__ .
'/includes/diff/ComplexityException.php',
'MediaWiki\\Diff\\WordAccumulator' => __DIR__ .
'/includes/diff/WordAccumulator.php',
+ 'MediaWiki\\Exception\\Renderer' => __DIR__ .
'/includes/exception/Renderer.php',
+ 'MediaWiki\\Exception\\StandardRenderer' => __DIR__ .
'/includes/exception/StandardRenderer.php',
'MediaWiki\\HeaderCallback' => __DIR__ . '/includes/HeaderCallback.php',
'MediaWiki\\Interwiki\\ClassicInterwikiLookup' => __DIR__ .
'/includes/interwiki/ClassicInterwikiLookup.php',
'MediaWiki\\Interwiki\\InterwikiLookup' => __DIR__ .
'/includes/interwiki/InterwikiLookup.php',
diff --git a/includes/MediaWiki.php b/includes/MediaWiki.php
index b18414d..1a83e12 100644
--- a/includes/MediaWiki.php
+++ b/includes/MediaWiki.php
@@ -542,7 +542,7 @@
$cache = new HTMLFileCache(
$context->getTitle(), 'view' );
if ( $cache->isCached() ) {
$cache->loadFromFileCache( $context,
HTMLFileCache::MODE_OUTAGE );
- print MWExceptionRenderer::getHTML( $e
);
+ print
MediaWikiServices::getInstance()->getExceptionRenderer()->getHTML( $e );
exit;
}
diff --git a/includes/MediaWikiServices.php b/includes/MediaWikiServices.php
index 3bf6d78..564417a 100644
--- a/includes/MediaWikiServices.php
+++ b/includes/MediaWikiServices.php
@@ -1,4 +1,5 @@
<?php
+
namespace MediaWiki;
use Config;
@@ -36,6 +37,7 @@
use TitleParser;
use VirtualRESTServiceClient;
use MediaWiki\Interwiki\InterwikiLookup;
+use MediaWiki\Exception\Renderer;
/**
* Service locator for MediaWiki core services.
@@ -672,6 +674,14 @@
return $this->getService( 'ReadOnlyMode' );
}
+ /**
+ * @since 1.30
+ * @return Renderer
+ */
+ public function getExceptionRenderer() {
+ return $this->getService( 'ExceptionRenderer' );
+ }
+
///////////////////////////////////////////////////////////////////////////
// NOTE: When adding a service getter here, don't forget to add a test
// case for it in MediaWikiServicesTest::provideGetters() and in
diff --git a/includes/ServiceWiring.php b/includes/ServiceWiring.php
index 6afabed..5a7335a 100644
--- a/includes/ServiceWiring.php
+++ b/includes/ServiceWiring.php
@@ -37,6 +37,7 @@
* MediaWiki code base.
*/
+use MediaWiki\Exception\StandardRenderer;
use MediaWiki\Interwiki\ClassicInterwikiLookup;
use MediaWiki\Linker\LinkRendererFactory;
use MediaWiki\Logger\LoggerFactory;
@@ -417,6 +418,10 @@
);
},
+ 'ExceptionRenderer' => function( MediaWikiServices $services ) {
+ return new StandardRenderer();
+ },
+
///////////////////////////////////////////////////////////////////////////
// NOTE: When adding a service here, don't forget to add a getter
function
// in the MediaWikiServices class. The convenience getter should just
call
diff --git a/includes/exception/MWExceptionHandler.php
b/includes/exception/MWExceptionHandler.php
index 433274e..aad0ffc 100644
--- a/includes/exception/MWExceptionHandler.php
+++ b/includes/exception/MWExceptionHandler.php
@@ -18,6 +18,7 @@
* @file
*/
+use MediaWiki\Exception\Renderer;
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
use Psr\Log\LogLevel;
@@ -64,6 +65,7 @@
* @param Exception|Throwable $e
*/
protected static function report( $e ) {
+ $renderer =
MediaWikiServices::getInstance()->getExceptionRenderer();
try {
// Try and show the exception prettily, with the normal
skin infrastructure
if ( $e instanceof MWException ) {
@@ -72,13 +74,13 @@
// removed.
$e->report();
} else {
- MWExceptionRenderer::output( $e,
MWExceptionRenderer::AS_PRETTY );
+ $renderer->output( $e, Renderer::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_RAW, $e2 );
+ $renderer->output( $e, Renderer::AS_RAW, $e2 );
}
}
diff --git a/includes/exception/MWExceptionRenderer.php
b/includes/exception/MWExceptionRenderer.php
index bd43934..e651b89 100644
--- a/includes/exception/MWExceptionRenderer.php
+++ b/includes/exception/MWExceptionRenderer.php
@@ -19,13 +19,12 @@
* @author Aaron Schulz
*/
-use Wikimedia\Rdbms\DBConnectionError;
-use Wikimedia\Rdbms\DBError;
-use Wikimedia\Rdbms\DBReadOnlyError;
-use Wikimedia\Rdbms\DBExpectedError;
+use MediaWiki\Exception\Renderer;
+use MediaWiki\MediaWikiServices;
/**
- * Class to expose exceptions to the client (API bots, users, admins using CLI
scripts)
+ * B/C wrapper for Renderer
+ * @deprecated since 1.30, use Renderer instead.
* @since 1.28
*/
class MWExceptionRenderer {
@@ -36,115 +35,10 @@
* @param Exception|Throwable $e Original exception
* @param integer $mode MWExceptionExposer::AS_* constant
* @param Exception|Throwable|null $eNew New exception from attempting
to show the first
+ * @deprecated since 1.30, use Renderer instead.
*/
public static function output( $e, $mode, $eNew = null ) {
- 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( $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 );
- if ( $e instanceof DBConnectionError ) {
- self::reportOutageHTML( $e );
- } else {
- 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 .= 'Original exception: ' .
-
MWExceptionHandler::getPublicLogMessage( $e );
- $message .= "\n\nException caught
inside exception handler.\n\n" .
- self::getShowBacktraceError( $e
);
- }
- $message .= "\n";
- } else {
- if ( self::showBackTrace( $e ) ) {
- $message =
MWExceptionHandler::getLogMessage( $e ) .
- "\nBacktrace:\n" .
-
MWExceptionHandler::getRedactedTraceAsString( $e ) . "\n";
- } else {
- $message =
MWExceptionHandler::getPublicLogMessage( $e );
- }
- }
- echo nl2br( htmlspecialchars( $message ) ) . "\n";
- }
- }
-
- /**
- * @param Exception|Throwable $e
- * @return bool Should the exception use $wgOut to output the error?
- */
- private static function useOutputPage( $e ) {
- // Can the extension use the Message class/wfMessage to get
i18n-ed messages?
- foreach ( $e->getTrace() as $frame ) {
- if ( isset( $frame['class'] ) && $frame['class'] ===
'LocalisationCache' ) {
- return false;
- }
- }
-
- return (
- !empty( $GLOBALS['wgFullyInitialised'] ) &&
- !empty( $GLOBALS['wgOut'] ) &&
- !defined( 'MEDIAWIKI_INSTALL' )
- );
- }
-
- /**
- * Output the exception report using HTML
- *
- * @param Exception|Throwable $e
- */
- private static function reportHTML( $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' ) );
- }
-
- // Show any custom GUI message before the details
- if ( $e instanceof MessageSpecifier ) {
- $wgOut->addHTML( Message::newFromSpecifier( $e
)->escaped() );
- }
- $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";
-
- echo self::getHTML( $e );
-
- echo "</body></html>\n";
- }
+ self::getRenderer()->output( $e, $mode, $eNew );
}
/**
@@ -154,213 +48,16 @@
*
* @param Exception|Throwable $e
* @return string Html to output
+ * @deprecated since 1.30, use Renderer instead.
*/
public static function getHTML( $e ) {
- if ( self::showBackTrace( $e ) ) {
- $html = "<div class=\"errorbox mw-content-ltr\"><p>" .
- nl2br( htmlspecialchars(
MWExceptionHandler::getLogMessage( $e ) ) ) .
- '</p><p>Backtrace:</p><p>' .
- nl2br( htmlspecialchars(
MWExceptionHandler::getRedactedTraceAsString( $e ) ) ) .
- "</p></div>\n";
- } else {
- $logId = WebRequest::getRequestId();
- $html = "<div class=\"errorbox mw-content-ltr\">" .
- '[' . $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" .
- "<!-- " . wordwrap(
self::getShowBacktraceError( $e ), 50 ) . " -->";
- }
-
- return $html;
+ return self::getRenderer()->getHTML( $e );
}
/**
- * 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
+ * @return Renderer
*/
- 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|Throwable $e
- * @return string
- */
- private static function getText( $e ) {
- if ( self::showBackTrace( $e ) ) {
- return MWExceptionHandler::getLogMessage( $e ) .
- "\nBacktrace:\n" .
- MWExceptionHandler::getRedactedTraceAsString(
$e ) . "\n";
- } else {
- return self::getShowBacktraceError( $e );
- }
- }
-
- /**
- * @param Exception|Throwable $e
- * @return bool
- */
- private static function showBackTrace( $e ) {
- global $wgShowExceptionDetails, $wgShowDBErrorBacktrace;
-
- return (
- $wgShowExceptionDetails &&
- ( !( $e instanceof DBError ) || $wgShowDBErrorBacktrace
)
- );
- }
-
- /**
- * @param Exception|Throwable $e
- * @return string
- */
- private static function getShowBacktraceError( $e ) {
- global $wgShowExceptionDetails, $wgShowDBErrorBacktrace;
- $vars = [];
- if ( !$wgShowExceptionDetails ) {
- $vars[] = '$wgShowExceptionDetails = true;';
- }
- if ( $e instanceof DBError && !$wgShowDBErrorBacktrace ) {
- $vars[] = '$wgShowDBErrorBacktrace = true;';
- }
- $vars = implode( ' and ', $vars );
- return "Set $vars at the bottom of LocalSettings.php to show
detailed debugging information\n";
- }
-
- /**
- * @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|Throwable $e
- */
- private static function reportOutageHTML( $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;
+ private static function getRenderer() {
+ return MediaWikiServices::getInstance()->getExceptionRenderer();
}
}
diff --git a/includes/exception/Renderer.php b/includes/exception/Renderer.php
new file mode 100644
index 0000000..1b87426
--- /dev/null
+++ b/includes/exception/Renderer.php
@@ -0,0 +1,251 @@
+<?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
+ */
+
+namespace MediaWiki\Exception;
+
+use Exception;
+use HttpStatus;
+use Message;
+use MessageSpecifier;
+use MWException;
+use Throwable;
+use Wikimedia\Rdbms\DBError;
+use Wikimedia\Rdbms\DBReadOnlyError;
+use Wikimedia\Rdbms\DBExpectedError;
+
+/**
+ * Interface to expose exceptions to the client (API bots, users, admins using
CLI scripts).
+ * @since 1.30
+ */
+abstract class Renderer {
+
+ /** Render exception as plain text */
+ const AS_RAW = 1;
+
+ /** Render exception as HTML */
+ const AS_PRETTY = 2;
+
+ /**
+ * Get the HTML for the error.
+ *
+ * This will be called in situations where giving the exception
renderer full control over
+ * the page is not appropriate, such as appending an error to HTML
loaded from file cache.
+ * (The default implementation also uses this internally.)
+ *
+ * @param Exception|Throwable $e
+ * @return string Html to output
+ */
+ abstract public function getHTML( $e );
+
+ /**
+ * Get the plain text for the error.
+ *
+ * @param Exception|Throwable $e
+ * @return string Text to output
+ */
+ abstract public function getText( $e );
+
+ /**
+ * Render the exception for web display.
+ *
+ * This method is expected to assume full control over the response
(HTML headers etc).
+ * @param Exception|Throwable $e Original exception
+ * @param int $mode MWExceptionRenderer::AS_* constant
+ * @param Exception|Throwable|null $eNew New exception from attempting
to show the first
+ */
+ abstract protected function doOutput( $e, $mode, $eNew = null );
+
+ /**
+ * Render the exception for display.
+ *
+ * This method is expected to assume full control over the response
(HTML headers etc).
+ * @param Exception|Throwable $e Original exception
+ * @param int $mode MWExceptionRenderer::AS_* constant
+ * @param Exception|Throwable|null $eNew New exception from attempting
to show the first
+ */
+ public function output( $e, $mode, $eNew = null ) {
+ if ( defined( 'MW_API' ) ) {
+ // Unhandled API exception, we can't be sure that
format printer is alive
+ $this->header( 'MediaWiki-API-Error:
internal_api_error_' . get_class( $e ) );
+ wfHttpError( 500, 'Internal Server Error',
$this->getText( $e ) );
+ } elseif ( $this->isCommandLine() ) {
+ $this->printError( $this->getText( $e ) );
+ } else {
+ $this->doOutput( $e, $mode, $eNew );
+ }
+ }
+
+ /**
+ * Add a status header, but only if it is safe to do.
+ * @param integer $code
+ */
+ protected function statusHeader( $code ) {
+ if ( !headers_sent() ) {
+ HttpStatus::header( $code );
+ }
+ }
+
+ /**
+ * Add a header, but only if it is safe to do.
+ * @param string $header
+ */
+ protected function header( $header ) {
+ if ( !headers_sent() ) {
+ header( $header );
+ }
+ }
+
+ /**
+ * Checks whether it is appropriate to use stdout or a similar
console-oriented display method.
+ * @return bool
+ */
+ protected function isCommandLine() {
+ return !empty( $GLOBALS['wgCommandLineMode'] );
+ }
+
+ /**
+ * Print a message, if possible to STDERR.
+ * Use this in command line mode only (see isCommandLine)
+ *
+ * @param string $message Failure text
+ */
+ protected 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( 'STDERR' ) ) {
+ fwrite( STDERR, $message );
+ } else {
+ echo $message;
+ }
+ }
+
+ /**
+ * Check whether it the site admin enabled the display of backtraces fo
the given exception type.
+ * @param Exception|Throwable $e
+ * @return bool
+ */
+ protected function showBackTrace( $e ) {
+ global $wgShowExceptionDetails, $wgShowDBErrorBacktrace;
+
+ return (
+ $wgShowExceptionDetails &&
+ ( !( $e instanceof DBError ) || $wgShowDBErrorBacktrace
)
+ );
+ }
+
+ /**
+ * Helper function to generate a message explaining how to enable
displaying stack traces
+ * for this exception.
+ * @param Exception|Throwable $e
+ * @return string
+ */
+ protected function getShowBacktraceError( $e ) {
+ global $wgShowExceptionDetails, $wgShowDBErrorBacktrace;
+ $vars = [];
+ if ( !$wgShowExceptionDetails ) {
+ $vars[] = '$wgShowExceptionDetails = true;';
+ }
+ if ( $e instanceof DBError && !$wgShowDBErrorBacktrace ) {
+ $vars[] = '$wgShowDBErrorBacktrace = true;';
+ }
+ $vars = implode( ' and ', $vars );
+ return "Set $vars at the bottom of LocalSettings.php to show
detailed debugging information\n";
+ }
+
+ /**
+ * Get the OutputPage object to use with a given exception.
+ * @param Exception|Throwable $e
+ * @return \OutputPage|null OutputPage object or null if it is not
available, or not advisable
+ * to use given the type of the exception.
+ */
+ protected function getOutputPage( $e ) {
+ global $wgOut;
+
+ // If the exception happened somewhere inside LocalisationCache
we cannot use i18n
+ // so it's probably better to fall back to a more barebones
display.
+ foreach ( $e->getTrace() as $frame ) {
+ if ( isset( $frame['class'] ) && $frame['class'] ===
'LocalisationCache' ) {
+ return null;
+ }
+ }
+
+ // make sure $wgOut is available
+ if (
+ empty( $GLOBALS['wgFullyInitialised'] ) ||
+ empty( $GLOBALS['wgOut'] ) ||
+ defined( 'MEDIAWIKI_INSTALL' )
+ ) {
+ return null;
+ }
+
+ return $wgOut;
+ }
+
+ /**
+ * Return the title of the error page for certain common exception
types.
+ * @param Exception|Throwable $e
+ * @return string
+ */
+ protected function getErrorPageTitle( $e ) {
+ if ( $e instanceof MWException ) {
+ return $e->getPageTitle();
+ } elseif ( $e instanceof DBReadOnlyError ) {
+ return $this->msg( 'readonly', 'Database is locked' );
+ } elseif ( $e instanceof DBExpectedError ) {
+ return $this->msg( 'databaseerror', 'Database error' );
+ } else {
+ return $this->msg( 'internalerror', 'Internal error' );
+ }
+ }
+
+ /**
+ * Try to get an extra message about this exception that's appropriate
to display before showing
+ * the exception details.
+ * @param Exception|Throwable $e
+ * @return Message|null
+ */
+ protected function getCustomMessage( $e ) {
+ if ( $e instanceof MessageSpecifier ) {
+ return Message::newFromSpecifier( $e );
+ }
+ return null;
+ }
+
+ /**
+ * 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
+ */
+ protected 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 );
+ }
+ }
+
+}
diff --git a/includes/exception/StandardRenderer.php
b/includes/exception/StandardRenderer.php
new file mode 100644
index 0000000..d09a50b
--- /dev/null
+++ b/includes/exception/StandardRenderer.php
@@ -0,0 +1,243 @@
+<?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
+ */
+
+namespace MediaWiki\Exception;
+
+use Exception;
+use MessageCache;
+use MWExceptionHandler;
+use Throwable;
+use WebRequest;
+use Wikimedia\Rdbms\DBConnectionError;
+
+/**
+ * Legacy Renderer implementation
+ * @since 1.30
+ */
+class StandardRenderer extends Renderer {
+
+ /**
+ * {@inheritdoc}
+ *
+ * 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.
+ */
+ public function getHTML( $e ) {
+ if ( $this->showBackTrace( $e ) ) {
+ $html = "<div class=\"errorbox mw-content-ltr\"><p>" .
+ nl2br( htmlspecialchars(
MWExceptionHandler::getLogMessage( $e ) ) ) .
+ '</p><p>Backtrace:</p><p>' .
+ nl2br( htmlspecialchars(
MWExceptionHandler::getRedactedTraceAsString( $e ) ) ) .
+ "</p></div>\n";
+ } else {
+ $logId = WebRequest::getRequestId();
+ $html = "<div class=\"errorbox mw-content-ltr\">" .
+ '[' . $logId . '] ' .
+ gmdate( 'Y-m-d H:i:s' ) . ": " .
+ $this->msg(
"internalerror-fatal-exception",
+ "Fatal exception of type $1",
+ get_class( $e ),
+ $logId,
+ MWExceptionHandler::getURL()
+ ) . "</div>\n" .
+ "<!-- " . wordwrap(
$this->getShowBacktraceError( $e ), 50 ) . " -->";
+ }
+
+ return $html;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getText( $e ) {
+ if ( $this->showBackTrace( $e ) ) {
+ return MWExceptionHandler::getLogMessage( $e ) .
+ "\nBacktrace:\n" .
+
MWExceptionHandler::getRedactedTraceAsString( $e ) . "\n";
+ } else {
+ return $this->getShowBacktraceError( $e );
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function doOutput( $e, $mode, $eNew = null ) {
+ global $wgMimeType;
+
+ if ( $mode === self::AS_PRETTY ) {
+ $this->statusHeader( 500 );
+ if ( $e instanceof DBConnectionError ) {
+ $this->reportOutageHTML( $e );
+ } else {
+ $this->header( "Content-Type: $wgMimeType;
charset=utf-8" );
+ $this->reportHTML( $e );
+ }
+ } else {
+ if ( $eNew ) {
+ $message = "MediaWiki internal error.\n\n";
+ if ( $this->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 .= 'Original exception: ' .
+
MWExceptionHandler::getPublicLogMessage( $e );
+ $message .= "\n\nException caught
inside exception handler.\n\n" .
+
$this->getShowBacktraceError( $e );
+ }
+ $message .= "\n";
+ } else {
+ if ( $this->showBackTrace( $e ) ) {
+ $message =
+
MWExceptionHandler::getLogMessage( $e ) . "\nBacktrace:\n" .
+
MWExceptionHandler::getRedactedTraceAsString( $e ) . "\n";
+ } else {
+ $message =
MWExceptionHandler::getPublicLogMessage( $e );
+ }
+ }
+ echo nl2br( htmlspecialchars( $message ) ) . "\n";
+ }
+ }
+
+ /**
+ * Output the exception report using HTML
+ *
+ * @param Exception|Throwable $e
+ */
+ private function reportHTML( $e ) {
+ global $wgSitename;
+
+ $outputPage = $this->getOutputPage( $e );
+ $pageTitle = $this->getErrorPageTitle( $e );
+ $customMessage = $this->getCustomMessage( $e );
+ if ( $outputPage ) {
+ $outputPage->prepareErrorPage( $pageTitle );
+
+ if ( $customMessage ) {
+ $outputPage->addHTML( $customMessage->escaped()
);
+ }
+ $outputPage->addHTML( $this->getHTML( $e ) );
+
+ $outputPage->output();
+ } else {
+ $this->header( 'Content-Type: text/html; charset=utf-8'
);
+ $pageTitle = $this->msg( 'internalerror', 'Internal
error' );
+ echo "<!DOCTYPE html>\n" .
+ '<html><head>' .
+ // Mimick OutputPage::setPageTitle behaviour
+ '<title>' .
+ htmlspecialchars( $this->msg( 'pagetitle', "$1
- $wgSitename", $pageTitle ) ) .
+ '</title>' .
+ '<style>body { font-family: sans-serif; margin:
0; padding: 0.5em 2em; }</style>' .
+ "</head><body>\n";
+
+ echo $this->getHTML( $e );
+
+ echo "</body></html>\n";
+ }
+ }
+
+ /**
+ * @param Exception|Throwable $e
+ */
+ private function reportOutageHTML( $e ) {
+ 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' ],
htmlspecialchars( $e->getMessage() ) ),
+ htmlspecialchars( $this->msg( 'dberr-info',
'($1)' ) )
+ );
+ } else {
+ $info = htmlspecialchars( $this->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 .= $this->googleSearchForm();
+
+ echo $html;
+ }
+
+ /**
+ * @return string
+ */
+ private function googleSearchForm() {
+ 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;
+ }
+}
diff --git a/tests/phpunit/includes/MediaWikiServicesTest.php
b/tests/phpunit/includes/MediaWikiServicesTest.php
index a72662f..82bd133 100644
--- a/tests/phpunit/includes/MediaWikiServicesTest.php
+++ b/tests/phpunit/includes/MediaWikiServicesTest.php
@@ -1,5 +1,7 @@
<?php
+
use Liuggio\StatsdClient\Factory\StatsdDataFactory;
+use MediaWiki\Exception\Renderer;
use MediaWiki\Interwiki\InterwikiLookup;
use MediaWiki\Linker\LinkRenderer;
use MediaWiki\Linker\LinkRendererFactory;
@@ -329,7 +331,8 @@
'MainObjectStash' => [ 'MainObjectStash',
BagOStuff::class ],
'MainWANObjectCache' => [ 'MainWANObjectCache',
WANObjectCache::class ],
'LocalServerObjectCache' => [ 'LocalServerObjectCache',
BagOStuff::class ],
- 'VirtualRESTServiceClient' => [
'VirtualRESTServiceClient', VirtualRESTServiceClient::class ]
+ 'VirtualRESTServiceClient' => [
'VirtualRESTServiceClient', VirtualRESTServiceClient::class ],
+ 'ExceptionRenderer' => [ 'ExceptionRenderer',
Renderer::class ],
];
}
--
To view, visit https://gerrit.wikimedia.org/r/355061
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Ifb01b454ec2fbe5f9ecb375a12b0e7e8a884ee22
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Gergő Tisza <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits