From: addshore Date: Mon, 24 Feb 2014 20:13:49 +0000 (+0100) Subject: Split Exception.php X-Git-Tag: 1.31.0-rc.0~16832^2 X-Git-Url: http://git.cyclocoop.org/%22.%24h.%22?a=commitdiff_plain;h=557af130868dacfb981e916baa4dcbcb866e16a1;p=lhc%2Fweb%2Fwiklou.git Split Exception.php Change-Id: I8273b342f8814887b65227457d0a461d7cd31e75 --- diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 54635e9c69..8d5b75d6c0 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -38,7 +38,6 @@ $wgAutoloadLocalClasses = array( 'AuthPlugin' => 'includes/AuthPlugin.php', 'AuthPluginUser' => 'includes/AuthPlugin.php', 'Autopromote' => 'includes/Autopromote.php', - 'BadTitleError' => 'includes/Exception.php', 'BaseTemplate' => 'includes/SkinTemplate.php', 'Block' => 'includes/Block.php', 'CacheHelper' => 'includes/CacheHelper.php', @@ -73,10 +72,8 @@ $wgAutoloadLocalClasses = array( 'DumpPipeOutput' => 'includes/Export.php', 'EditPage' => 'includes/EditPage.php', 'EmailNotification' => 'includes/UserMailer.php', - 'ErrorPageError' => 'includes/Exception.php', 'FakeTitle' => 'includes/FakeTitle.php', 'Fallback' => 'includes/Fallback.php', - 'FatalError' => 'includes/Exception.php', 'FauxRequest' => 'includes/WebRequest.php', 'FauxResponse' => 'includes/WebResponse.php', 'FeedItem' => 'includes/Feed.php', @@ -116,7 +113,6 @@ $wgAutoloadLocalClasses = array( 'HTMLTextAreaField' => 'includes/htmlform/HTMLTextAreaField.php', 'HTMLTextField' => 'includes/htmlform/HTMLTextField.php', 'Http' => 'includes/HttpFunctions.php', - 'HttpError' => 'includes/Exception.php', 'ICacheHelper' => 'includes/CacheHelper.php', 'IcuCollation' => 'includes/Collation.php', 'IdentityCollation' => 'includes/Collation.php', @@ -148,8 +144,6 @@ $wgAutoloadLocalClasses = array( 'Message' => 'includes/Message.php', 'MessageBlobStore' => 'includes/MessageBlobStore.php', 'MimeMagic' => 'includes/MimeMagic.php', - 'MWException' => 'includes/Exception.php', - 'MWExceptionHandler' => 'includes/Exception.php', 'MWHookException' => 'includes/Hooks.php', 'MWHttpRequest' => 'includes/HttpFunctions.php', 'MWInit' => 'includes/Init.php', @@ -161,7 +155,6 @@ $wgAutoloadLocalClasses = array( 'PasswordError' => 'includes/User.php', 'PathRouter' => 'includes/PathRouter.php', 'PathRouterPatternReplacer' => 'includes/PathRouter.php', - 'PermissionsError' => 'includes/Exception.php', 'PhpHttpRequest' => 'includes/HttpFunctions.php', 'PoolCounter' => 'includes/PoolCounter.php', 'PoolCounter_Stub' => 'includes/PoolCounter.php', @@ -175,7 +168,6 @@ $wgAutoloadLocalClasses = array( 'QueryPage' => 'includes/QueryPage.php', 'QuickTemplate' => 'includes/SkinTemplate.php', 'RawMessage' => 'includes/Message.php', - 'ReadOnlyError' => 'includes/Exception.php', 'RedirectSpecialArticle' => 'includes/specialpage/RedirectSpecialPage.php', 'RedirectSpecialPage' => 'includes/specialpage/RedirectSpecialPage.php', 'ReverseChronologicalPager' => 'includes/Pager.php', @@ -216,15 +208,12 @@ $wgAutoloadLocalClasses = array( 'Title' => 'includes/Title.php', 'TitleArray' => 'includes/TitleArray.php', 'TitleArrayFromResult' => 'includes/TitleArrayFromResult.php', - 'ThrottledError' => 'includes/Exception.php', 'UnlistedSpecialPage' => 'includes/specialpage/UnlistedSpecialPage.php', 'UploadSourceAdapter' => 'includes/Import.php', 'UppercaseCollation' => 'includes/Collation.php', 'User' => 'includes/User.php', 'UserArray' => 'includes/UserArray.php', 'UserArrayFromResult' => 'includes/UserArrayFromResult.php', - 'UserBlockedError' => 'includes/Exception.php', - 'UserNotLoggedIn' => 'includes/Exception.php', 'UserCache' => 'includes/cache/UserCache.php', 'UserMailer' => 'includes/UserMailer.php', 'UserRightsProxy' => 'includes/UserRightsProxy.php', @@ -526,6 +515,19 @@ $wgAutoloadLocalClasses = array( 'WikiDiff3' => 'includes/diff/WikiDiff3.php', 'WordLevelDiff' => 'includes/diff/DairikiDiff.php', + # includes/exception + 'UserBlockedError' => 'includes/exception/UserBlockedError.php', + 'UserNotLoggedIn' => 'includes/exception/UserNotLoggedIn.php', + 'ThrottledError' => 'includes/exception/ThrottledError.php', + 'ReadOnlyError' => 'includes/exception/ReadOnlyError.php', + 'PermissionsError' => 'includes/exception/PermissionsError.php', + 'MWException' => 'includes/exception/MWException.php', + 'MWExceptionHandler' => 'includes/exception/MWExceptionHandler.php', + 'HttpError' => 'includes/exception/HttpError.php', + 'BadTitleError' => 'includes/exception/BadTitleError.php', + 'ErrorPageError' => 'includes/exception/ErrorPageError.php', + 'FatalError' => 'includes/exception/FatalError.php', + # includes/externalstore 'ExternalStore' => 'includes/externalstore/ExternalStore.php', 'ExternalStoreDB' => 'includes/externalstore/ExternalStoreDB.php', diff --git a/includes/Exception.php b/includes/Exception.php deleted file mode 100644 index e91a178f43..0000000000 --- a/includes/Exception.php +++ /dev/null @@ -1,918 +0,0 @@ -useMessageCache() && - !empty( $GLOBALS['wgFullyInitialised'] ) && - !empty( $GLOBALS['wgOut'] ) && - !defined( 'MEDIAWIKI_INSTALL' ); - } - - /** - * Whether to log this exception in the exception debug log. - * - * @since 1.23 - * @return boolean - */ - function isLoggable() { - return true; - } - - /** - * Can the extension use the Message class/wfMessage to get i18n-ed messages? - * - * @return bool - */ - 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 - */ - 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 - */ - function msg( $key, $fallback /*[, params...] */ ) { - $args = array_slice( func_get_args(), 2 ); - - if ( $this->useMessageCache() ) { - return wfMessage( $key, $args )->plain(); - } else { - 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 - */ - function getHTML() { - global $wgShowExceptionDetails; - - if ( $wgShowExceptionDetails ) { - return '

' . nl2br( htmlspecialchars( MWExceptionHandler::getLogMessage( $this ) ) ) . - '

Backtrace:

' . nl2br( htmlspecialchars( MWExceptionHandler::getRedactedTraceAsString( $this ) ) ) . - "

\n"; - } else { - return "
" . - '[' . MWExceptionHandler::getLogId( $this ) . '] ' . - gmdate( 'Y-m-d H:i:s' ) . - ": Fatal exception of type " . get_class( $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 - */ - 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 - */ - function getPageTitle() { - global $wgSitename; - return $this->msg( 'pagetitle', "$1 - $wgSitename", $this->msg( 'internalerror', 'Internal error' ) ); - } - - /** - * Get a the ID for this error. - * - * @since 1.20 - * @deprecated since 1.22 Use MWExceptionHandler::getLogId instead. - * @return string - */ - function getLogId() { - wfDeprecated( __METHOD__, '1.22' ); - return MWExceptionHandler::getLogId( $this ); - } - - /** - * Return the requested URL and point to file and line number from which the - * exception occurred - * - * @since 1.8 - * @deprecated since 1.22 Use MWExceptionHandler::getLogMessage instead. - * @return string - */ - function getLogMessage() { - wfDeprecated( __METHOD__, '1.22' ); - return MWExceptionHandler::getLogMessage( $this ); - } - - /** - * Output the exception report using HTML. - */ - function reportHTML() { - global $wgOut; - 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 { - header( 'Content-Type: text/html; charset=utf-8' ); - echo "\n" . - '' . - '' . htmlspecialchars( $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(). - */ - function report() { - global $wgMimeType; - - MWExceptionHandler::logException( $this ); - - if ( defined( 'MW_API' ) ) { - // Unhandled API exception, we can't be sure that format printer is alive - 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 { - header( 'HTTP/1.1 500 MediaWiki exception' ); - header( 'Status: 500 MediaWiki exception', true ); - header( "Content-Type: $wgMimeType; charset=utf-8", true ); - - $this->reportHTML(); - } - } - - /** - * Check whether we are in command line mode or not to report the exception - * in the correct format. - * - * @return bool - */ - static function isCommandLine() { - return !empty( $GLOBALS['wgCommandLineMode'] ); - } -} - -/** - * Exception class which takes an HTML error message, and does not - * produce a backtrace. Replacement for OutputPage::fatalError(). - * - * @since 1.7 - * @ingroup Exception - */ -class FatalError extends MWException { - - /** - * @return string - */ - function getHTML() { - return $this->getMessage(); - } - - /** - * @return string - */ - function getText() { - return $this->getMessage(); - } -} - -/** - * An error page which can definitely be safely rendered using the OutputPage. - * - * @since 1.7 - * @ingroup Exception - */ -class ErrorPageError extends MWException { - public $title, $msg, $params; - - /** - * Note: these arguments are keys into wfMessage(), not text! - * - * @param string|Message $title Message key (string) for page title, or a Message object - * @param string|Message $msg Message key (string) for error text, or a Message object - * @param array $params with parameters to wfMessage() - */ - function __construct( $title, $msg, $params = array() ) { - $this->title = $title; - $this->msg = $msg; - $this->params = $params; - - // Bug 44111: Messages in the log files should be in English and not - // customized by the local wiki. So get the default English version for - // passing to the parent constructor. Our overridden report() below - // makes sure that the page shown to the user is not forced to English. - if ( $msg instanceof Message ) { - $enMsg = clone( $msg ); - } else { - $enMsg = wfMessage( $msg, $params ); - } - $enMsg->inLanguage( 'en' )->useDatabase( false ); - parent::__construct( $enMsg->text() ); - } - - function report() { - global $wgOut; - - $wgOut->showErrorPage( $this->title, $this->msg, $this->params ); - $wgOut->output(); - } -} - -/** - * Show an error page on a badtitle. - * Similar to ErrorPage, but emit a 400 HTTP error code to let mobile - * browser it is not really a valid content. - * - * @since 1.19 - * @ingroup Exception - */ -class BadTitleError extends ErrorPageError { - /** - * @param string|Message $msg A message key (default: 'badtitletext') - * @param array $params parameter to wfMessage() - */ - function __construct( $msg = 'badtitletext', $params = array() ) { - parent::__construct( 'badtitle', $msg, $params ); - } - - /** - * Just like ErrorPageError::report() but additionally set - * a 400 HTTP status code (bug 33646). - */ - function report() { - global $wgOut; - - // bug 33646: a badtitle error page need to return an error code - // to let mobile browser now that it is not a normal page. - $wgOut->setStatusCode( 400 ); - parent::report(); - } - -} - -/** - * Show an error when a user tries to do something they do not have the necessary - * permissions for. - * - * @since 1.18 - * @ingroup Exception - */ -class PermissionsError extends ErrorPageError { - public $permission, $errors; - - function __construct( $permission, $errors = array() ) { - global $wgLang; - - $this->permission = $permission; - - if ( !count( $errors ) ) { - $groups = array_map( - array( 'User', 'makeGroupLinkWiki' ), - User::getGroupsWithPermission( $this->permission ) - ); - - if ( $groups ) { - $errors[] = array( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) ); - } else { - $errors[] = array( 'badaccess-group0' ); - } - } - - $this->errors = $errors; - } - - function report() { - global $wgOut; - - $wgOut->showPermissionsErrorPage( $this->errors, $this->permission ); - $wgOut->output(); - } -} - -/** - * Show an error when the wiki is locked/read-only and the user tries to do - * something that requires write access. - * - * @since 1.18 - * @ingroup Exception - */ -class ReadOnlyError extends ErrorPageError { - public function __construct() { - parent::__construct( - 'readonly', - 'readonlytext', - wfReadOnlyReason() ?: array() - ); - } -} - -/** - * Show an error when the user hits a rate limit. - * - * @since 1.18 - * @ingroup Exception - */ -class ThrottledError extends ErrorPageError { - public function __construct() { - parent::__construct( - 'actionthrottled', - 'actionthrottledtext' - ); - } - - public function report() { - global $wgOut; - $wgOut->setStatusCode( 503 ); - parent::report(); - } -} - -/** - * Show an error when the user tries to do something whilst blocked. - * - * @since 1.18 - * @ingroup Exception - */ -class UserBlockedError extends ErrorPageError { - public function __construct( Block $block ) { - // @todo FIXME: Implement a more proper way to get context here. - $params = $block->getPermissionsError( RequestContext::getMain() ); - parent::__construct( 'blockedtitle', array_shift( $params ), $params ); - } -} - -/** - * Shows a generic "user is not logged in" error page. - * - * This is essentially an ErrorPageError exception which by default uses the - * 'exception-nologin' as a title and 'exception-nologin-text' for the message. - * @see bug 37627 - * @since 1.20 - * - * @par Example: - * @code - * if( $user->isAnon() ) { - * throw new UserNotLoggedIn(); - * } - * @endcode - * - * Note the parameter order differs from ErrorPageError, this allows you to - * simply specify a reason without overriding the default title. - * - * @par Example: - * @code - * if( $user->isAnon() ) { - * throw new UserNotLoggedIn( 'action-require-loggedin' ); - * } - * @endcode - * - * @ingroup Exception - */ -class UserNotLoggedIn extends ErrorPageError { - - /** - * @param string $reasonMsg A message key containing the reason for the error. - * Optional, default: 'exception-nologin-text' - * @param string $titleMsg A message key to set the page title. - * Optional, default: 'exception-nologin' - * @param array $params Parameters to wfMessage(). - * Optional, default: array() - */ - public function __construct( - $reasonMsg = 'exception-nologin-text', - $titleMsg = 'exception-nologin', - $params = array() - ) { - parent::__construct( $titleMsg, $reasonMsg, $params ); - } -} - -/** - * Show an error that looks like an HTTP server error. - * Replacement for wfHttpError(). - * - * @since 1.19 - * @ingroup Exception - */ -class HttpError extends MWException { - private $httpCode, $header, $content; - - /** - * Constructor - * - * @param $httpCode Integer: HTTP status code to send to the client - * @param string|Message $content content of the message - * @param string|Message $header content of the header (\ and \) - */ - public function __construct( $httpCode, $content, $header = null ) { - parent::__construct( $content ); - $this->httpCode = (int)$httpCode; - $this->header = $header; - $this->content = $content; - } - - /** - * Returns the HTTP status code supplied to the constructor. - * - * @return int - */ - public function getStatusCode() { - return $this->httpCode; - } - - /** - * Report the HTTP error. - * Sends the appropriate HTTP status code and outputs an - * HTML page with an error message. - */ - public function report() { - $httpMessage = HttpStatus::getMessage( $this->httpCode ); - - header( "Status: {$this->httpCode} {$httpMessage}", true, $this->httpCode ); - header( 'Content-type: text/html; charset=utf-8' ); - - print $this->getHTML(); - } - - /** - * Returns HTML for reporting the HTTP error. - * This will be a minimal but complete HTML document. - * - * @return string HTML - */ - public function getHTML() { - if ( $this->header === null ) { - $header = HttpStatus::getMessage( $this->httpCode ); - } elseif ( $this->header instanceof Message ) { - $header = $this->header->escaped(); - } else { - $header = htmlspecialchars( $this->header ); - } - - if ( $this->content instanceof Message ) { - $content = $this->content->escaped(); - } else { - $content = htmlspecialchars( $this->content ); - } - - return "\n" . - "$header\n" . - "

$header

$content

\n"; - } -} - -/** - * Handler class for MWExceptions - * @ingroup Exception - */ -class MWExceptionHandler { - /** - * Install an exception handler for MediaWiki exception types. - */ - public static function installHandler() { - set_exception_handler( array( 'MWExceptionHandler', 'handle' ) ); - } - - /** - * Report an exception to the user - */ - protected static function report( Exception $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 error 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 { - $message = "Unexpected non-MediaWiki exception encountered, of type \"" . - get_class( $e ) . "\""; - - if ( $wgShowExceptionDetails ) { - $message .= "\n" . MWExceptionHandler::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; - } - } - - /** - * Exception handler which simulates the appropriate catch() handling: - * - * try { - * ... - * } catch ( MWException $e ) { - * $e->report(); - * } catch ( Exception $e ) { - * echo $e->__toString(); - * } - */ - public static function handle( $e ) { - global $wgFullyInitialised; - - self::report( $e ); - - // Final cleanup - if ( $wgFullyInitialised ) { - try { - // uses $wgRequest, hence the $wgFullyInitialised condition - wfLogProfilingData(); - } catch ( Exception $e ) { - } - } - - // Exit value should be nonzero for the benefit of shell jobs - exit( 1 ); - } - - /** - * Generate a string representation of an exception's stack trace - * - * Like Exception::getTraceAsString, but replaces argument values with - * argument type or class name. - * - * @param Exception $e - * @return string - */ - public static function getRedactedTraceAsString( Exception $e ) { - $text = ''; - - foreach ( self::getRedactedTrace( $e ) as $level => $frame ) { - if ( isset( $frame['file'] ) && isset( $frame['line'] ) ) { - $text .= "#{$level} {$frame['file']}({$frame['line']}): "; - } else { - // 'file' and 'line' are unset for calls via call_user_func (bug 55634) - // This matches behaviour of Exception::getTraceAsString to instead - // display "[internal function]". - $text .= "#{$level} [internal function]: "; - } - - if ( isset( $frame['class'] ) ) { - $text .= $frame['class'] . $frame['type'] . $frame['function']; - } else { - $text .= $frame['function']; - } - - if ( isset( $frame['args'] ) ) { - $text .= '(' . implode( ', ', $frame['args'] ) . ")\n"; - } else { - $text .= "()\n"; - } - } - - $level = $level + 1; - $text .= "#{$level} {main}"; - - return $text; - } - - /** - * Return a copy of an exception's backtrace as an array. - * - * Like Exception::getTrace, but replaces each element in each frame's - * argument array with the name of its class (if the element is an object) - * or its type (if the element is a PHP primitive). - * - * @since 1.22 - * @param Exception $e - * @return array - */ - public static function getRedactedTrace( Exception $e ) { - return array_map( function ( $frame ) { - if ( isset( $frame['args'] ) ) { - $frame['args'] = array_map( function ( $arg ) { - return is_object( $arg ) ? get_class( $arg ) : gettype( $arg ); - }, $frame['args'] ); - } - return $frame; - }, $e->getTrace() ); - } - - /** - * Get the ID for this error. - * - * The ID is saved so that one can match the one output to the user (when - * $wgShowExceptionDetails is set to false), to the entry in the debug log. - * - * @since 1.22 - * @param Exception $e - * @return string - */ - public static function getLogId( Exception $e ) { - if ( !isset( $e->_mwLogId ) ) { - $e->_mwLogId = wfRandomString( 8 ); - } - return $e->_mwLogId; - } - - /** - * If the exception occurred in the course of responding to a request, - * returns the requested URL. Otherwise, returns false. - * - * @since 1.23 - * @return string|bool - */ - public static function getURL() { - global $wgRequest; - if ( !isset( $wgRequest ) || $wgRequest instanceof FauxRequest ) { - return false; - } - return $wgRequest->getRequestURL(); - } - - /** - * Return the requested URL and point to file and line number from which the - * exception occurred. - * - * @since 1.22 - * @param Exception $e - * @return string - */ - public static function getLogMessage( Exception $e ) { - $id = self::getLogId( $e ); - $file = $e->getFile(); - $line = $e->getLine(); - $message = $e->getMessage(); - $url = self::getURL() ?: '[no req]'; - - return "[$id] $url Exception from line $line of $file: $message"; - } - - /** - * Serialize an Exception object to JSON. - * - * The JSON object will have keys 'id', 'file', 'line', 'message', and - * 'url'. These keys map to string values, with the exception of 'line', - * which is a number, and 'url', which may be either a string URL or or - * null if the exception did not occur in the context of serving a web - * request. - * - * If $wgLogExceptionBacktrace is true, it will also have a 'backtrace' - * key, mapped to the array return value of Exception::getTrace, but with - * each element in each frame's "args" array (if set) replaced with the - * argument's class name (if the argument is an object) or type name (if - * the argument is a PHP primitive). - * - * @par Sample JSON record ($wgLogExceptionBacktrace = false): - * @code - * { - * "id": "c41fb419", - * "file": "/var/www/mediawiki/includes/cache/MessageCache.php", - * "line": 704, - * "message": "Non-string key given", - * "url": "/wiki/Main_Page" - * } - * @endcode - * - * @par Sample JSON record ($wgLogExceptionBacktrace = true): - * @code - * { - * "id": "dc457938", - * "file": "/vagrant/mediawiki/includes/cache/MessageCache.php", - * "line": 704, - * "message": "Non-string key given", - * "url": "/wiki/Main_Page", - * "backtrace": [{ - * "file": "/vagrant/mediawiki/extensions/VisualEditor/VisualEditor.hooks.php", - * "line": 80, - * "function": "get", - * "class": "MessageCache", - * "type": "->", - * "args": ["array"] - * }] - * } - * @endcode - * - * @since 1.23 - * @param Exception $e - * @param bool $pretty Add non-significant whitespace to improve readability (default: false). - * @param int $escaping Bitfield consisting of FormatJson::.*_OK class constants. - * @return string|bool: JSON string if successful; false upon failure - */ - public static function jsonSerializeException( Exception $e, $pretty = false, $escaping = 0 ) { - global $wgLogExceptionBacktrace; - - $exceptionData = array( - 'id' => self::getLogId( $e ), - 'file' => $e->getFile(), - 'line' => $e->getLine(), - 'message' => $e->getMessage(), - ); - - // Because MediaWiki is first and foremost a web application, we set a - // 'url' key unconditionally, but set it to null if the exception does - // not occur in the context of a web request, as a way of making that - // fact visible and explicit. - $exceptionData['url'] = self::getURL() ?: null; - - if ( $wgLogExceptionBacktrace ) { - // Argument values may not be serializable, so redact them. - $exceptionData['backtrace'] = self::getRedactedTrace( $e ); - } - - return FormatJson::encode( $exceptionData, $pretty, $escaping ); - } - - /** - * Log an exception to the exception log (if enabled). - * - * This method must not assume the exception is an MWException, - * it is also used to handle PHP errors or errors from other libraries. - * - * @since 1.22 - * @param Exception $e - */ - public static function logException( Exception $e ) { - global $wgLogExceptionBacktrace; - - if ( !( $e instanceof MWException ) || $e->isLoggable() ) { - $log = self::getLogMessage( $e ); - if ( $wgLogExceptionBacktrace ) { - wfDebugLog( 'exception', $log . "\n" . $e->getTraceAsString() ); - } else { - wfDebugLog( 'exception', $log ); - } - - $json = self::jsonSerializeException( $e, false, FormatJson::ALL_OK ); - if ( $json !== false ) { - wfDebugLog( 'exception-json', $json, 'private' ); - } - } - - } - -} diff --git a/includes/exception/BadTitleError.php b/includes/exception/BadTitleError.php new file mode 100644 index 0000000000..b3d842d78b --- /dev/null +++ b/includes/exception/BadTitleError.php @@ -0,0 +1,51 @@ +setStatusCode( 400 ); + parent::report(); + } + +} diff --git a/includes/exception/ErrorPageError.php b/includes/exception/ErrorPageError.php new file mode 100644 index 0000000000..439d8e445e --- /dev/null +++ b/includes/exception/ErrorPageError.php @@ -0,0 +1,61 @@ +title = $title; + $this->msg = $msg; + $this->params = $params; + + // Bug 44111: Messages in the log files should be in English and not + // customized by the local wiki. So get the default English version for + // passing to the parent constructor. Our overridden report() below + // makes sure that the page shown to the user is not forced to English. + if ( $msg instanceof Message ) { + $enMsg = clone( $msg ); + } else { + $enMsg = wfMessage( $msg, $params ); + } + $enMsg->inLanguage( 'en' )->useDatabase( false ); + parent::__construct( $enMsg->text() ); + } + + function report() { + global $wgOut; + + $wgOut->showErrorPage( $this->title, $this->msg, $this->params ); + $wgOut->output(); + } +} diff --git a/includes/exception/FatalError.php b/includes/exception/FatalError.php new file mode 100644 index 0000000000..8953219f14 --- /dev/null +++ b/includes/exception/FatalError.php @@ -0,0 +1,43 @@ +getMessage(); + } + + /** + * @return string + */ + function getText() { + return $this->getMessage(); + } +} diff --git a/includes/exception/HttpError.php b/includes/exception/HttpError.php new file mode 100644 index 0000000000..f955f06b2f --- /dev/null +++ b/includes/exception/HttpError.php @@ -0,0 +1,93 @@ + and \) + */ + public function __construct( $httpCode, $content, $header = null ) { + parent::__construct( $content ); + $this->httpCode = (int)$httpCode; + $this->header = $header; + $this->content = $content; + } + + /** + * Returns the HTTP status code supplied to the constructor. + * + * @return int + */ + public function getStatusCode() { + return $this->httpCode; + } + + /** + * Report the HTTP error. + * Sends the appropriate HTTP status code and outputs an + * HTML page with an error message. + */ + public function report() { + $httpMessage = HttpStatus::getMessage( $this->httpCode ); + + header( "Status: {$this->httpCode} {$httpMessage}", true, $this->httpCode ); + header( 'Content-type: text/html; charset=utf-8' ); + + print $this->getHTML(); + } + + /** + * Returns HTML for reporting the HTTP error. + * This will be a minimal but complete HTML document. + * + * @return string HTML + */ + public function getHTML() { + if ( $this->header === null ) { + $header = HttpStatus::getMessage( $this->httpCode ); + } elseif ( $this->header instanceof Message ) { + $header = $this->header->escaped(); + } else { + $header = htmlspecialchars( $this->header ); + } + + if ( $this->content instanceof Message ) { + $content = $this->content->escaped(); + } else { + $content = htmlspecialchars( $this->content ); + } + + return "\n" . + "$header\n" . + "

$header

$content

\n"; + } +} diff --git a/includes/exception/MWException.php b/includes/exception/MWException.php new file mode 100644 index 0000000000..80b6ac5c4b --- /dev/null +++ b/includes/exception/MWException.php @@ -0,0 +1,273 @@ +useMessageCache() && + !empty( $GLOBALS['wgFullyInitialised'] ) && + !empty( $GLOBALS['wgOut'] ) && + !defined( 'MEDIAWIKI_INSTALL' ); + } + + /** + * Whether to log this exception in the exception debug log. + * + * @since 1.23 + * @return boolean + */ + function isLoggable() { + return true; + } + + /** + * Can the extension use the Message class/wfMessage to get i18n-ed messages? + * + * @return bool + */ + 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 + */ + 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 + */ + function msg( $key, $fallback /*[, params...] */ ) { + $args = array_slice( func_get_args(), 2 ); + + if ( $this->useMessageCache() ) { + return wfMessage( $key, $args )->plain(); + } else { + 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 + */ + function getHTML() { + global $wgShowExceptionDetails; + + if ( $wgShowExceptionDetails ) { + return '

' . nl2br( htmlspecialchars( MWExceptionHandler::getLogMessage( $this ) ) ) . + '

Backtrace:

' . nl2br( htmlspecialchars( MWExceptionHandler::getRedactedTraceAsString( $this ) ) ) . + "

\n"; + } else { + return "
" . + '[' . MWExceptionHandler::getLogId( $this ) . '] ' . + gmdate( 'Y-m-d H:i:s' ) . + ": Fatal exception of type " . get_class( $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 + */ + 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 + */ + function getPageTitle() { + global $wgSitename; + return $this->msg( 'pagetitle', "$1 - $wgSitename", $this->msg( 'internalerror', 'Internal error' ) ); + } + + /** + * Get a the ID for this error. + * + * @since 1.20 + * @deprecated since 1.22 Use MWExceptionHandler::getLogId instead. + * @return string + */ + function getLogId() { + wfDeprecated( __METHOD__, '1.22' ); + return MWExceptionHandler::getLogId( $this ); + } + + /** + * Return the requested URL and point to file and line number from which the + * exception occurred + * + * @since 1.8 + * @deprecated since 1.22 Use MWExceptionHandler::getLogMessage instead. + * @return string + */ + function getLogMessage() { + wfDeprecated( __METHOD__, '1.22' ); + return MWExceptionHandler::getLogMessage( $this ); + } + + /** + * Output the exception report using HTML. + */ + function reportHTML() { + global $wgOut; + 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 { + header( 'Content-Type: text/html; charset=utf-8' ); + echo "\n" . + '' . + '' . htmlspecialchars( $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(). + */ + function report() { + global $wgMimeType; + + MWExceptionHandler::logException( $this ); + + if ( defined( 'MW_API' ) ) { + // Unhandled API exception, we can't be sure that format printer is alive + 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 { + header( 'HTTP/1.1 500 MediaWiki exception' ); + header( 'Status: 500 MediaWiki exception', true ); + header( "Content-Type: $wgMimeType; charset=utf-8", true ); + + $this->reportHTML(); + } + } + + /** + * Check whether we are in command line mode or not to report the exception + * in the correct format. + * + * @return bool + */ + static function isCommandLine() { + return !empty( $GLOBALS['wgCommandLineMode'] ); + } +} diff --git a/includes/exception/MWExceptionHandler.php b/includes/exception/MWExceptionHandler.php new file mode 100644 index 0000000000..64e8999781 --- /dev/null +++ b/includes/exception/MWExceptionHandler.php @@ -0,0 +1,349 @@ +report(); + } catch ( Exception $e2 ) { + // Exception occurred from within exception handler + // Show a simpler error 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 { + $message = "Unexpected non-MediaWiki exception encountered, of type \"" . + get_class( $e ) . "\""; + + if ( $wgShowExceptionDetails ) { + $message .= "\n" . MWExceptionHandler::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; + } + } + + /** + * Exception handler which simulates the appropriate catch() handling: + * + * try { + * ... + * } catch ( MWException $e ) { + * $e->report(); + * } catch ( Exception $e ) { + * echo $e->__toString(); + * } + */ + public static function handle( $e ) { + global $wgFullyInitialised; + + self::report( $e ); + + // Final cleanup + if ( $wgFullyInitialised ) { + try { + // uses $wgRequest, hence the $wgFullyInitialised condition + wfLogProfilingData(); + } catch ( Exception $e ) { + } + } + + // Exit value should be nonzero for the benefit of shell jobs + exit( 1 ); + } + + /** + * Generate a string representation of an exception's stack trace + * + * Like Exception::getTraceAsString, but replaces argument values with + * argument type or class name. + * + * @param Exception $e + * @return string + */ + public static function getRedactedTraceAsString( Exception $e ) { + $text = ''; + + foreach ( self::getRedactedTrace( $e ) as $level => $frame ) { + if ( isset( $frame['file'] ) && isset( $frame['line'] ) ) { + $text .= "#{$level} {$frame['file']}({$frame['line']}): "; + } else { + // 'file' and 'line' are unset for calls via call_user_func (bug 55634) + // This matches behaviour of Exception::getTraceAsString to instead + // display "[internal function]". + $text .= "#{$level} [internal function]: "; + } + + if ( isset( $frame['class'] ) ) { + $text .= $frame['class'] . $frame['type'] . $frame['function']; + } else { + $text .= $frame['function']; + } + + if ( isset( $frame['args'] ) ) { + $text .= '(' . implode( ', ', $frame['args'] ) . ")\n"; + } else { + $text .= "()\n"; + } + } + + $level = $level + 1; + $text .= "#{$level} {main}"; + + return $text; + } + + /** + * Return a copy of an exception's backtrace as an array. + * + * Like Exception::getTrace, but replaces each element in each frame's + * argument array with the name of its class (if the element is an object) + * or its type (if the element is a PHP primitive). + * + * @since 1.22 + * @param Exception $e + * @return array + */ + public static function getRedactedTrace( Exception $e ) { + return array_map( function ( $frame ) { + if ( isset( $frame['args'] ) ) { + $frame['args'] = array_map( function ( $arg ) { + return is_object( $arg ) ? get_class( $arg ) : gettype( $arg ); + }, $frame['args'] ); + } + return $frame; + }, $e->getTrace() ); + } + + /** + * Get the ID for this error. + * + * The ID is saved so that one can match the one output to the user (when + * $wgShowExceptionDetails is set to false), to the entry in the debug log. + * + * @since 1.22 + * @param Exception $e + * @return string + */ + public static function getLogId( Exception $e ) { + if ( !isset( $e->_mwLogId ) ) { + $e->_mwLogId = wfRandomString( 8 ); + } + return $e->_mwLogId; + } + + /** + * If the exception occurred in the course of responding to a request, + * returns the requested URL. Otherwise, returns false. + * + * @since 1.23 + * @return string|bool + */ + public static function getURL() { + global $wgRequest; + if ( !isset( $wgRequest ) || $wgRequest instanceof FauxRequest ) { + return false; + } + return $wgRequest->getRequestURL(); + } + + /** + * Return the requested URL and point to file and line number from which the + * exception occurred. + * + * @since 1.22 + * @param Exception $e + * @return string + */ + public static function getLogMessage( Exception $e ) { + $id = self::getLogId( $e ); + $file = $e->getFile(); + $line = $e->getLine(); + $message = $e->getMessage(); + $url = self::getURL() ?: '[no req]'; + + return "[$id] $url Exception from line $line of $file: $message"; + } + + /** + * Serialize an Exception object to JSON. + * + * The JSON object will have keys 'id', 'file', 'line', 'message', and + * 'url'. These keys map to string values, with the exception of 'line', + * which is a number, and 'url', which may be either a string URL or or + * null if the exception did not occur in the context of serving a web + * request. + * + * If $wgLogExceptionBacktrace is true, it will also have a 'backtrace' + * key, mapped to the array return value of Exception::getTrace, but with + * each element in each frame's "args" array (if set) replaced with the + * argument's class name (if the argument is an object) or type name (if + * the argument is a PHP primitive). + * + * @par Sample JSON record ($wgLogExceptionBacktrace = false): + * @code + * { + * "id": "c41fb419", + * "file": "/var/www/mediawiki/includes/cache/MessageCache.php", + * "line": 704, + * "message": "Non-string key given", + * "url": "/wiki/Main_Page" + * } + * @endcode + * + * @par Sample JSON record ($wgLogExceptionBacktrace = true): + * @code + * { + * "id": "dc457938", + * "file": "/vagrant/mediawiki/includes/cache/MessageCache.php", + * "line": 704, + * "message": "Non-string key given", + * "url": "/wiki/Main_Page", + * "backtrace": [{ + * "file": "/vagrant/mediawiki/extensions/VisualEditor/VisualEditor.hooks.php", + * "line": 80, + * "function": "get", + * "class": "MessageCache", + * "type": "->", + * "args": ["array"] + * }] + * } + * @endcode + * + * @since 1.23 + * @param Exception $e + * @param bool $pretty Add non-significant whitespace to improve readability (default: false). + * @param int $escaping Bitfield consisting of FormatJson::.*_OK class constants. + * @return string|bool: JSON string if successful; false upon failure + */ + public static function jsonSerializeException( Exception $e, $pretty = false, $escaping = 0 ) { + global $wgLogExceptionBacktrace; + + $exceptionData = array( + 'id' => self::getLogId( $e ), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'message' => $e->getMessage(), + ); + + // Because MediaWiki is first and foremost a web application, we set a + // 'url' key unconditionally, but set it to null if the exception does + // not occur in the context of a web request, as a way of making that + // fact visible and explicit. + $exceptionData['url'] = self::getURL() ?: null; + + if ( $wgLogExceptionBacktrace ) { + // Argument values may not be serializable, so redact them. + $exceptionData['backtrace'] = self::getRedactedTrace( $e ); + } + + return FormatJson::encode( $exceptionData, $pretty, $escaping ); + } + + /** + * Log an exception to the exception log (if enabled). + * + * This method must not assume the exception is an MWException, + * it is also used to handle PHP errors or errors from other libraries. + * + * @since 1.22 + * @param Exception $e + */ + public static function logException( Exception $e ) { + global $wgLogExceptionBacktrace; + + if ( !( $e instanceof MWException ) || $e->isLoggable() ) { + $log = self::getLogMessage( $e ); + if ( $wgLogExceptionBacktrace ) { + wfDebugLog( 'exception', $log . "\n" . $e->getTraceAsString() ); + } else { + wfDebugLog( 'exception', $log ); + } + + $json = self::jsonSerializeException( $e, false, FormatJson::ALL_OK ); + if ( $json !== false ) { + wfDebugLog( 'exception-json', $json, 'private' ); + } + } + + } + +} diff --git a/includes/exception/PermissionsError.php b/includes/exception/PermissionsError.php new file mode 100644 index 0000000000..987c894d56 --- /dev/null +++ b/includes/exception/PermissionsError.php @@ -0,0 +1,58 @@ +permission = $permission; + + if ( !count( $errors ) ) { + $groups = array_map( + array( 'User', 'makeGroupLinkWiki' ), + User::getGroupsWithPermission( $this->permission ) + ); + + if ( $groups ) { + $errors[] = array( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) ); + } else { + $errors[] = array( 'badaccess-group0' ); + } + } + + $this->errors = $errors; + } + + function report() { + global $wgOut; + + $wgOut->showPermissionsErrorPage( $this->errors, $this->permission ); + $wgOut->output(); + } +} diff --git a/includes/exception/ReadOnlyError.php b/includes/exception/ReadOnlyError.php new file mode 100644 index 0000000000..cebeb1c3eb --- /dev/null +++ b/includes/exception/ReadOnlyError.php @@ -0,0 +1,36 @@ +setStatusCode( 503 ); + parent::report(); + } +} diff --git a/includes/exception/UserBlockedError.php b/includes/exception/UserBlockedError.php new file mode 100644 index 0000000000..9d19f8b63c --- /dev/null +++ b/includes/exception/UserBlockedError.php @@ -0,0 +1,33 @@ +getPermissionsError( RequestContext::getMain() ); + parent::__construct( 'blockedtitle', array_shift( $params ), $params ); + } +} diff --git a/includes/exception/UserNotLoggedIn.php b/includes/exception/UserNotLoggedIn.php new file mode 100644 index 0000000000..9d89009100 --- /dev/null +++ b/includes/exception/UserNotLoggedIn.php @@ -0,0 +1,65 @@ +isAnon() ) { + * throw new UserNotLoggedIn(); + * } + * @endcode + * + * Note the parameter order differs from ErrorPageError, this allows you to + * simply specify a reason without overriding the default title. + * + * @par Example: + * @code + * if( $user->isAnon() ) { + * throw new UserNotLoggedIn( 'action-require-loggedin' ); + * } + * @endcode + * + * @ingroup Exception + */ +class UserNotLoggedIn extends ErrorPageError { + + /** + * @param string $reasonMsg A message key containing the reason for the error. + * Optional, default: 'exception-nologin-text' + * @param string $titleMsg A message key to set the page title. + * Optional, default: 'exception-nologin' + * @param array $params Parameters to wfMessage(). + * Optional, default: array() + */ + public function __construct( + $reasonMsg = 'exception-nologin-text', + $titleMsg = 'exception-nologin', + $params = array() + ) { + parent::__construct( $titleMsg, $reasonMsg, $params ); + } +} diff --git a/tests/phpunit/includes/ExceptionTest.php b/tests/phpunit/includes/ExceptionTest.php deleted file mode 100644 index eaef1f7afb..0000000000 --- a/tests/phpunit/includes/ExceptionTest.php +++ /dev/null @@ -1,117 +0,0 @@ -assertNotEquals( false, $json, - "The $exception_class exception should be JSON serializable, got false." ); - } - - function provideExceptionClasses() { - return array( - array( 'Exception' ), - array( 'MWException' ), - ); - } - - /** - * Lame JSON schema validation. - * - * @covers MWExceptionHandler::jsonSerializeException - * - * @param $expectedKeyType String Type expected as returned by gettype() - * @param $exClass String An exception class (ie: Exception, MWException) - * @param $key String Name of the key to validate in the serialized JSON - * @dataProvider provideJsonSerializedKeys - */ - function testJsonserializeexceptionKeys( $expectedKeyType, $exClass, $key ) { - - # Make sure we log a backtrace: - $this->setMwGlobals( array( 'wgLogExceptionBacktrace' => true ) ); - - $json = json_decode( - MWExceptionHandler::jsonSerializeException( new $exClass()) - ); - $this->assertObjectHasAttribute( $key, $json, - "JSON serialized exception is missing key '$key'" - ); - $this->assertInternalType( $expectedKeyType, $json->$key, - "JSON serialized key '$key' has type " . gettype( $json->$key ) - . " (expected: $expectedKeyType)." - ); - } - - /** - * Returns test cases: exception class, key name, gettype() - */ - function provideJsonSerializedKeys() { - $testCases = array(); - foreach ( array( 'Exception', 'MWException' ) as $exClass ) { - $exTests = array( - array( 'string', $exClass, 'id' ), - array( 'string', $exClass, 'file' ), - array( 'integer', $exClass, 'line' ), - array( 'string', $exClass, 'message' ), - array( 'null', $exClass, 'url' ), - # Backtrace only enabled with wgLogExceptionBacktrace = true - array( 'array', $exClass, 'backtrace' ), - ); - $testCases = array_merge( $testCases, $exTests ); - } - return $testCases; - } - - /** - * Given wgLogExceptionBacktrace is true - * then serialized exception SHOULD have a backtrace - * - * @covers MWExceptionHandler::jsonSerializeException - */ - function testJsonserializeexceptionBacktracingEnabled() { - $this->setMwGlobals( array( 'wgLogExceptionBacktrace' => true ) ); - $json = json_decode( - MWExceptionHandler::jsonSerializeException( new Exception() ) - ); - $this->assertObjectHasAttribute( 'backtrace', $json ); - } - - /** - * Given wgLogExceptionBacktrace is false - * then serialized exception SHOULD NOT have a backtrace - * - * @covers MWExceptionHandler::jsonSerializeException - */ - function testJsonserializeexceptionBacktracingDisabled() { - $this->setMwGlobals( array( 'wgLogExceptionBacktrace' => false ) ); - $json = json_decode( - MWExceptionHandler::jsonSerializeException( new Exception() ) - ); - $this->assertObjectNotHasAttribute( 'backtrace', $json ); - - } - -} diff --git a/tests/phpunit/includes/MWExceptionHandlerTest.php b/tests/phpunit/includes/MWExceptionHandlerTest.php deleted file mode 100644 index dd76e3bc9e..0000000000 --- a/tests/phpunit/includes/MWExceptionHandlerTest.php +++ /dev/null @@ -1,76 +0,0 @@ -getTrace(); - $hasObject = false; - $hasArray = false; - foreach ( $trace as $frame ) { - if ( ! isset( $frame['args'] ) ) { - continue; - } - foreach ( $frame['args'] as $arg ) { - $hasObject = $hasObject || is_object( $arg ); - $hasArray = $hasArray || is_array( $arg ); - } - - if ( $hasObject && $hasArray ) { - break; - } - } - $this->assertTrue( $hasObject, - "The stacktrace must have a function having an object has parameter" ); - $this->assertTrue( $hasArray, - "The stacktrace must have a function having an array has parameter" ); - - # Now we redact the trace.. and make sure no function arguments are - # arrays or objects. - $redacted = MWExceptionHandler::getRedactedTrace( $e ); - - foreach ( $redacted as $frame ) { - if ( ! isset( $frame['args'] ) ) { - continue; - } - foreach ( $frame['args'] as $arg ) { - $this->assertNotInternalType( 'array', $arg ); - $this->assertNotInternalType( 'object', $arg ); - } - } - - $this->assertEquals( 'value', $refvar, 'Ensuring reference variable wasn\'t changed' ); - } - - /** - * Helper function for testExpandArgumentsInCall - * - * Pass it an object and an array, and something by reference :-) - * - * @throws Exception - */ - protected static function helperThrowAnException( $a, $b, &$c ) { - throw new Exception(); - } -} diff --git a/tests/phpunit/includes/exception/MWExceptionHandlerTest.php b/tests/phpunit/includes/exception/MWExceptionHandlerTest.php new file mode 100644 index 0000000000..67faf8db82 --- /dev/null +++ b/tests/phpunit/includes/exception/MWExceptionHandlerTest.php @@ -0,0 +1,74 @@ +getTrace(); + $hasObject = false; + $hasArray = false; + foreach ( $trace as $frame ) { + if ( ! isset( $frame['args'] ) ) { + continue; + } + foreach ( $frame['args'] as $arg ) { + $hasObject = $hasObject || is_object( $arg ); + $hasArray = $hasArray || is_array( $arg ); + } + + if ( $hasObject && $hasArray ) { + break; + } + } + $this->assertTrue( $hasObject, + "The stacktrace must have a function having an object has parameter" ); + $this->assertTrue( $hasArray, + "The stacktrace must have a function having an array has parameter" ); + + # Now we redact the trace.. and make sure no function arguments are + # arrays or objects. + $redacted = MWExceptionHandler::getRedactedTrace( $e ); + + foreach ( $redacted as $frame ) { + if ( ! isset( $frame['args'] ) ) { + continue; + } + foreach ( $frame['args'] as $arg ) { + $this->assertNotInternalType( 'array', $arg ); + $this->assertNotInternalType( 'object', $arg ); + } + } + + $this->assertEquals( 'value', $refvar, 'Ensuring reference variable wasn\'t changed' ); + } + + /** + * Helper function for testExpandArgumentsInCall + * + * Pass it an object and an array, and something by reference :-) + * + * @throws Exception + */ + protected static function helperThrowAnException( $a, $b, &$c ) { + throw new Exception(); + } +} diff --git a/tests/phpunit/includes/exception/MWExceptionTest.php b/tests/phpunit/includes/exception/MWExceptionTest.php new file mode 100644 index 0000000000..5f001c7ea6 --- /dev/null +++ b/tests/phpunit/includes/exception/MWExceptionTest.php @@ -0,0 +1,115 @@ +assertNotEquals( false, $json, + "The $exception_class exception should be JSON serializable, got false." ); + } + + function provideExceptionClasses() { + return array( + array( 'Exception' ), + array( 'MWException' ), + ); + } + + /** + * Lame JSON schema validation. + * + * @covers MWExceptionHandler::jsonSerializeException + * + * @param $expectedKeyType String Type expected as returned by gettype() + * @param $exClass String An exception class (ie: Exception, MWException) + * @param $key String Name of the key to validate in the serialized JSON + * @dataProvider provideJsonSerializedKeys + */ + function testJsonserializeexceptionKeys( $expectedKeyType, $exClass, $key ) { + + # Make sure we log a backtrace: + $this->setMwGlobals( array( 'wgLogExceptionBacktrace' => true ) ); + + $json = json_decode( + MWExceptionHandler::jsonSerializeException( new $exClass()) + ); + $this->assertObjectHasAttribute( $key, $json, + "JSON serialized exception is missing key '$key'" + ); + $this->assertInternalType( $expectedKeyType, $json->$key, + "JSON serialized key '$key' has type " . gettype( $json->$key ) + . " (expected: $expectedKeyType)." + ); + } + + /** + * Returns test cases: exception class, key name, gettype() + */ + function provideJsonSerializedKeys() { + $testCases = array(); + foreach ( array( 'Exception', 'MWException' ) as $exClass ) { + $exTests = array( + array( 'string', $exClass, 'id' ), + array( 'string', $exClass, 'file' ), + array( 'integer', $exClass, 'line' ), + array( 'string', $exClass, 'message' ), + array( 'null', $exClass, 'url' ), + # Backtrace only enabled with wgLogExceptionBacktrace = true + array( 'array', $exClass, 'backtrace' ), + ); + $testCases = array_merge( $testCases, $exTests ); + } + return $testCases; + } + + /** + * Given wgLogExceptionBacktrace is true + * then serialized exception SHOULD have a backtrace + * + * @covers MWExceptionHandler::jsonSerializeException + */ + function testJsonserializeexceptionBacktracingEnabled() { + $this->setMwGlobals( array( 'wgLogExceptionBacktrace' => true ) ); + $json = json_decode( + MWExceptionHandler::jsonSerializeException( new Exception() ) + ); + $this->assertObjectHasAttribute( 'backtrace', $json ); + } + + /** + * Given wgLogExceptionBacktrace is false + * then serialized exception SHOULD NOT have a backtrace + * + * @covers MWExceptionHandler::jsonSerializeException + */ + function testJsonserializeexceptionBacktracingDisabled() { + $this->setMwGlobals( array( 'wgLogExceptionBacktrace' => false ) ); + $json = json_decode( + MWExceptionHandler::jsonSerializeException( new Exception() ) + ); + $this->assertObjectNotHasAttribute( 'backtrace', $json ); + + } + +}