useMessageCache() &&
!empty( $GLOBALS['wgFullyInitialised'] ) &&
!empty( $GLOBALS['wgOut'] ) &&
!defined( 'MEDIAWIKI_INSTALL' );
}
/**
* Whether to log this exception in the exception debug log.
*
* @since 1.23
* @return bool
*/
public function isLoggable() {
return true;
}
/**
* Can the extension use the Message class/wfMessage to get i18n-ed messages?
*
* @return bool
*/
public function useMessageCache() {
global $wgLang;
foreach ( $this->getTrace() as $frame ) {
if ( isset( $frame['class'] ) && $frame['class'] === 'LocalisationCache' ) {
return false;
}
}
return $wgLang instanceof Language;
}
/**
* Run hook to allow extensions to modify the text of the exception
*
* @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 function runHooks( $name, $args = array() ) {
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( array( $this ), $args );
foreach ( $hooks as $hook ) {
if (
is_string( $hook ) ||
( is_array( $hook ) && count( $hook ) >= 2 && is_string( $hook[0] ) )
) {
// 'function' or array( 'class', hook' )
$result = call_user_func_array( $hook, $callargs );
} else {
$result = null;
}
if ( is_string( $result ) ) {
return $result;
}
}
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
*/
public function msg( $key, $fallback /*[, params...] */ ) {
$args = array_slice( func_get_args(), 2 );
if ( $this->useMessageCache() ) {
try {
return wfMessage( $key, $args )->text();
} catch ( Exception $e ) {
}
}
return wfMsgReplaceArgs( $fallback, $args );
}
/**
* 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.
*
* @return string Html to output
*/
public function getHTML() {
global $wgShowExceptionDetails;
if ( $wgShowExceptionDetails ) {
return '
' . nl2br( htmlspecialchars( MWExceptionHandler::getLogMessage( $this ) ) ) .
'
Backtrace:
' .
nl2br( htmlspecialchars( MWExceptionHandler::getRedactedTraceAsString( $this ) ) ) .
"
\n";
} else {
$logId = MWExceptionHandler::getLogId( $this );
$type = get_class( $this );
return "" .
'[' . $logId . '] ' .
gmdate( 'Y-m-d H:i:s' ) . ": " .
$this->msg( "internalerror-fatal-exception",
"Fatal exception of type $1",
$type,
$logId,
MWExceptionHandler::getURL( $this )
) . "
\n" .
"";
}
}
/**
* Get the text to display when reporting the error on the command line.
* If $wgShowExceptionDetails is true, return a text message with a
* backtrace to the error.
*
* @return string
*/
public function getText() {
global $wgShowExceptionDetails;
if ( $wgShowExceptionDetails ) {
return MWExceptionHandler::getLogMessage( $this ) .
"\nBacktrace:\n" . MWExceptionHandler::getRedactedTraceAsString( $this ) . "\n";
} else {
return "Set \$wgShowExceptionDetails = true; " .
"in LocalSettings.php to show detailed debugging information.\n";
}
}
/**
* Return the title of the page when reporting this error in a HTTP response.
*
* @return string
*/
public function getPageTitle() {
return $this->msg( 'internalerror', 'Internal error' );
}
/**
* Output the exception report using HTML.
*/
public function reportHTML() {
global $wgOut, $wgSitename;
if ( $this->useOutputPage() ) {
$wgOut->prepareErrorPage( $this->getPageTitle() );
$hookResult = $this->runHooks( get_class( $this ) );
if ( $hookResult ) {
$wgOut->addHTML( $hookResult );
} else {
$wgOut->addHTML( $this->getHTML() );
}
$wgOut->output();
} else {
self::header( 'Content-Type: text/html; charset=utf-8' );
echo "\n" .
'' .
// Mimick OutputPage::setPageTitle behaviour
'' .
htmlspecialchars( $this->msg( 'pagetitle', "$1 - $wgSitename", $this->getPageTitle() ) ) .
'' .
'' .
"\n";
$hookResult = $this->runHooks( get_class( $this ) . 'Raw' );
if ( $hookResult ) {
echo $hookResult;
} else {
echo $this->getHTML();
}
echo "\n";
}
}
/**
* Output a report about the exception and takes care of formatting.
* 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();
}
}
/**
* Check whether we are in command line mode or not to report the exception
* in the correct format.
*
* @return bool
*/
public static function isCommandLine() {
return !empty( $GLOBALS['wgCommandLineMode'] );
}
/**
* Send a header, if we haven't already sent them. We shouldn't,
* but sometimes we might in a weird case like Export
* @param string $header
*/
private static function header( $header ) {
if ( !headers_sent() ) {
header( $header );
}
}
private static function statusHeader( $code ) {
if ( !headers_sent() ) {
HttpStatus::header( $code );
}
}
}