summaryrefslogtreecommitdiff
path: root/www/wiki/includes/exception
diff options
context:
space:
mode:
authorYaco <franco@reevo.org>2020-06-04 11:01:00 -0300
committerYaco <franco@reevo.org>2020-06-04 11:01:00 -0300
commitfc7369835258467bf97eb64f184b93691f9a9fd5 (patch)
treedaabd60089d2dd76d9f5fb416b005fbe159c799d /www/wiki/includes/exception
first commit
Diffstat (limited to 'www/wiki/includes/exception')
-rw-r--r--www/wiki/includes/exception/BadRequestError.php34
-rw-r--r--www/wiki/includes/exception/BadTitleError.php49
-rw-r--r--www/wiki/includes/exception/CannotCreateActorException.php29
-rw-r--r--www/wiki/includes/exception/ErrorPageError.php72
-rw-r--r--www/wiki/includes/exception/FatalError.php43
-rw-r--r--www/wiki/includes/exception/HttpError.php129
-rw-r--r--www/wiki/includes/exception/LocalizedException.php66
-rw-r--r--www/wiki/includes/exception/MWContentSerializationException.php8
-rw-r--r--www/wiki/includes/exception/MWException.php230
-rw-r--r--www/wiki/includes/exception/MWExceptionHandler.php697
-rw-r--r--www/wiki/includes/exception/MWExceptionRenderer.php338
-rw-r--r--www/wiki/includes/exception/MWUnknownContentModelException.php25
-rw-r--r--www/wiki/includes/exception/PermissionsError.php72
-rw-r--r--www/wiki/includes/exception/ProcOpenError.php29
-rw-r--r--www/wiki/includes/exception/ReadOnlyError.php36
-rw-r--r--www/wiki/includes/exception/ShellDisabledError.php32
-rw-r--r--www/wiki/includes/exception/ThrottledError.php40
-rw-r--r--www/wiki/includes/exception/UserBlockedError.php33
-rw-r--r--www/wiki/includes/exception/UserNotLoggedIn.php104
19 files changed, 2066 insertions, 0 deletions
diff --git a/www/wiki/includes/exception/BadRequestError.php b/www/wiki/includes/exception/BadRequestError.php
new file mode 100644
index 00000000..5fcf0e62
--- /dev/null
+++ b/www/wiki/includes/exception/BadRequestError.php
@@ -0,0 +1,34 @@
+<?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
+ */
+
+/**
+ * An error page that emits an HTTP 400 Bad Request status code.
+ *
+ * @since 1.28
+ * @ingroup Exception
+ */
+class BadRequestError extends ErrorPageError {
+
+ public function report() {
+ global $wgOut;
+ $wgOut->setStatusCode( 400 );
+ parent::report();
+ }
+}
diff --git a/www/wiki/includes/exception/BadTitleError.php b/www/wiki/includes/exception/BadTitleError.php
new file mode 100644
index 00000000..40c18a42
--- /dev/null
+++ b/www/wiki/includes/exception/BadTitleError.php
@@ -0,0 +1,49 @@
+<?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
+ */
+
+/**
+ * Show an error page on a badtitle.
+ *
+ * Uses BadRequestError to emit a 400 HTTP error code to ensure caching proxies and
+ * mobile browsers know not to cache it as valid content. (T35646)
+ *
+ * @since 1.19
+ * @ingroup Exception
+ */
+class BadTitleError extends BadRequestError {
+ /**
+ * @param string|Message|MalformedTitleException $msg A message key (default: 'badtitletext'), or
+ * a MalformedTitleException to figure out things from
+ * @param array $params Parameter to wfMessage()
+ */
+ public function __construct( $msg = 'badtitletext', $params = [] ) {
+ if ( $msg instanceof MalformedTitleException ) {
+ $errorMessage = $msg->getErrorMessage();
+ if ( !$errorMessage ) {
+ parent::__construct( 'badtitle', 'badtitletext', [] );
+ } else {
+ $errorMessageParams = $msg->getErrorMessageParameters();
+ parent::__construct( 'badtitle', $errorMessage, $errorMessageParams );
+ }
+ } else {
+ parent::__construct( 'badtitle', $msg, $params );
+ }
+ }
+}
diff --git a/www/wiki/includes/exception/CannotCreateActorException.php b/www/wiki/includes/exception/CannotCreateActorException.php
new file mode 100644
index 00000000..7c7ccfc4
--- /dev/null
+++ b/www/wiki/includes/exception/CannotCreateActorException.php
@@ -0,0 +1,29 @@
+<?php
+/**
+ * Exception thrown when some operation failed
+ *
+ * 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
+ *
+ * @since 1.31
+ */
+
+/**
+ * Exception thrown when an actor can't be created.
+ */
+class CannotCreateActorException extends RuntimeException {
+}
diff --git a/www/wiki/includes/exception/ErrorPageError.php b/www/wiki/includes/exception/ErrorPageError.php
new file mode 100644
index 00000000..4b181267
--- /dev/null
+++ b/www/wiki/includes/exception/ErrorPageError.php
@@ -0,0 +1,72 @@
+<?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
+ */
+
+/**
+ * An error page which can definitely be safely rendered using the OutputPage.
+ *
+ * @since 1.7
+ * @ingroup Exception
+ */
+class ErrorPageError extends MWException implements ILocalizedException {
+ 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 Array with parameters to wfMessage()
+ */
+ public function __construct( $title, $msg, $params = [] ) {
+ $this->title = $title;
+ $this->msg = $msg;
+ $this->params = $params;
+
+ // T46111: 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.
+ $enMsg = $this->getMessageObject();
+ $enMsg->inLanguage( 'en' )->useDatabase( false );
+ parent::__construct( $enMsg->text() );
+ }
+
+ /**
+ * Return a Message object for this exception
+ * @since 1.29
+ * @return Message
+ */
+ public function getMessageObject() {
+ if ( $this->msg instanceof Message ) {
+ return clone $this->msg;
+ }
+ return wfMessage( $this->msg, $this->params );
+ }
+
+ public function report() {
+ if ( self::isCommandLine() || defined( 'MW_API' ) ) {
+ parent::report();
+ } else {
+ global $wgOut;
+ $wgOut->showErrorPage( $this->title, $this->msg, $this->params );
+ $wgOut->output();
+ }
+ }
+}
diff --git a/www/wiki/includes/exception/FatalError.php b/www/wiki/includes/exception/FatalError.php
new file mode 100644
index 00000000..a7d672fa
--- /dev/null
+++ b/www/wiki/includes/exception/FatalError.php
@@ -0,0 +1,43 @@
+<?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
+ */
+
+/**
+ * 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
+ */
+ public function getHTML() {
+ return $this->getMessage();
+ }
+
+ /**
+ * @return string
+ */
+ public function getText() {
+ return $this->getMessage();
+ }
+}
diff --git a/www/wiki/includes/exception/HttpError.php b/www/wiki/includes/exception/HttpError.php
new file mode 100644
index 00000000..f464d8af
--- /dev/null
+++ b/www/wiki/includes/exception/HttpError.php
@@ -0,0 +1,129 @@
+<?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
+ */
+
+use MediaWiki\Logger\LoggerFactory;
+
+/**
+ * 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;
+
+ /**
+ * @param int $httpCode HTTP status code to send to the client
+ * @param string|Message $content Content of the message
+ * @param string|Message|null $header Content of the header (\<title\> and \<h1\>)
+ */
+ public function __construct( $httpCode, $content, $header = null ) {
+ parent::__construct( $content );
+ $this->httpCode = (int)$httpCode;
+ $this->header = $header;
+ $this->content = $content;
+ }
+
+ /**
+ * We don't want the default exception logging as we got our own logging set
+ * up in self::report.
+ *
+ * @see MWException::isLoggable
+ *
+ * @since 1.24
+ * @return bool
+ */
+ public function isLoggable() {
+ return false;
+ }
+
+ /**
+ * Returns the HTTP status code supplied to the constructor.
+ *
+ * @return int
+ */
+ public function getStatusCode() {
+ return $this->httpCode;
+ }
+
+ /**
+ * Report and log the HTTP error.
+ * Sends the appropriate HTTP status code and outputs an
+ * HTML page with an error message.
+ */
+ public function report() {
+ $this->doLog();
+
+ HttpStatus::header( $this->httpCode );
+ header( 'Content-type: text/html; charset=utf-8' );
+
+ print $this->getHTML();
+ }
+
+ private function doLog() {
+ $logger = LoggerFactory::getInstance( 'HttpError' );
+ $content = $this->content;
+
+ if ( $content instanceof Message ) {
+ $content = $content->text();
+ }
+
+ $context = [
+ 'file' => $this->getFile(),
+ 'line' => $this->getLine(),
+ 'http_code' => $this->httpCode,
+ ];
+
+ $logMsg = "$content ({http_code}) from {file}:{line}";
+
+ if ( $this->getStatusCode() < 500 ) {
+ $logger->info( $logMsg, $context );
+ } else {
+ $logger->error( $logMsg, $context );
+ }
+ }
+
+ /**
+ * 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 ) {
+ $titleHtml = htmlspecialchars( HttpStatus::getMessage( $this->httpCode ) );
+ } elseif ( $this->header instanceof Message ) {
+ $titleHtml = $this->header->escaped();
+ } else {
+ $titleHtml = htmlspecialchars( $this->header );
+ }
+
+ if ( $this->content instanceof Message ) {
+ $contentHtml = $this->content->escaped();
+ } else {
+ $contentHtml = nl2br( htmlspecialchars( $this->content ) );
+ }
+
+ return "<!DOCTYPE html>\n" .
+ "<html><head><title>$titleHtml</title></head>\n" .
+ "<body><h1>$titleHtml</h1><p>$contentHtml</p></body></html>\n";
+ }
+}
diff --git a/www/wiki/includes/exception/LocalizedException.php b/www/wiki/includes/exception/LocalizedException.php
new file mode 100644
index 00000000..f5f8c84e
--- /dev/null
+++ b/www/wiki/includes/exception/LocalizedException.php
@@ -0,0 +1,66 @@
+<?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
+ */
+
+/**
+ * Interface for MediaWiki-localized exceptions
+ *
+ * @since 1.29
+ * @ingroup Exception
+ */
+interface ILocalizedException {
+ /**
+ * Return a Message object for this exception
+ * @return Message
+ */
+ public function getMessageObject();
+}
+
+/**
+ * Basic localized exception.
+ *
+ * @since 1.29
+ * @ingroup Exception
+ * @note Don't use this in a situation where MessageCache is not functional.
+ */
+class LocalizedException extends Exception implements ILocalizedException {
+ /** @var string|array|MessageSpecifier */
+ protected $messageSpec;
+
+ /**
+ * @param string|array|MessageSpecifier $messageSpec See Message::newFromSpecifier
+ * @param int $code
+ * @param Exception|Throwable $previous The previous exception used for the exception chaining.
+ */
+ public function __construct( $messageSpec, $code = 0, $previous = null ) {
+ $this->messageSpec = $messageSpec;
+
+ // Exception->getMessage() should be in plain English, not localized.
+ // So fetch the English version of the message, without local
+ // customizations, and make a basic attempt to turn markup into text.
+ $msg = $this->getMessageObject()->inLanguage( 'en' )->useDatabase( false )->text();
+ $msg = preg_replace( '!</?(var|kbd|samp|code)>!', '"', $msg );
+ $msg = Sanitizer::stripAllTags( $msg );
+ parent::__construct( $msg, $code, $previous );
+ }
+
+ public function getMessageObject() {
+ return Message::newFromSpecifier( $this->messageSpec );
+ }
+}
diff --git a/www/wiki/includes/exception/MWContentSerializationException.php b/www/wiki/includes/exception/MWContentSerializationException.php
new file mode 100644
index 00000000..500cf7ce
--- /dev/null
+++ b/www/wiki/includes/exception/MWContentSerializationException.php
@@ -0,0 +1,8 @@
+<?php
+/**
+ * Exception representing a failure to serialize or unserialize a content object.
+ *
+ * @ingroup Content
+ */
+class MWContentSerializationException extends MWException {
+}
diff --git a/www/wiki/includes/exception/MWException.php b/www/wiki/includes/exception/MWException.php
new file mode 100644
index 00000000..b3e9422b
--- /dev/null
+++ b/www/wiki/includes/exception/MWException.php
@@ -0,0 +1,230 @@
+<?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
+ */
+
+/**
+ * MediaWiki exception
+ *
+ * @ingroup Exception
+ */
+class MWException extends Exception {
+ /**
+ * Should the exception use $wgOut to output the error?
+ *
+ * @return bool
+ */
+ public function useOutputPage() {
+ return $this->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::class ) {
+ return false;
+ }
+ }
+
+ return $wgLang instanceof Language;
+ }
+
+ /**
+ * 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 '<p>' . nl2br( htmlspecialchars( MWExceptionHandler::getLogMessage( $this ) ) ) .
+ '</p><p>Backtrace:</p><p>' .
+ nl2br( htmlspecialchars( MWExceptionHandler::getRedactedTraceAsString( $this ) ) ) .
+ "</p>\n";
+ } else {
+ $logId = WebRequest::getRequestId();
+ $type = static::class;
+ return Html::errorBox(
+ htmlspecialchars(
+ '[' . $logId . '] ' .
+ gmdate( 'Y-m-d H:i:s' ) . ": " .
+ $this->msg( "internalerror-fatal-exception",
+ "Fatal exception of type $1",
+ $type,
+ $logId,
+ MWExceptionHandler::getURL( $this )
+ )
+ ) ) .
+ "<!-- Set \$wgShowExceptionDetails = true; " .
+ "at the bottom of LocalSettings.php to show detailed " .
+ "debugging information. -->";
+ }
+ }
+
+ /**
+ * 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() );
+
+ $wgOut->addHTML( $this->getHTML() );
+
+ $wgOut->output();
+ } else {
+ self::header( 'Content-Type: text/html; charset=utf-8' );
+ echo "<!DOCTYPE html>\n" .
+ '<html><head>' .
+ // Mimick OutputPage::setPageTitle behaviour
+ '<title>' .
+ htmlspecialchars( $this->msg( 'pagetitle', "$1 - $wgSitename", $this->getPageTitle() ) ) .
+ '</title>' .
+ '<style>body { font-family: sans-serif; margin: 0; padding: 0.5em 2em; }</style>' .
+ "</head><body>\n";
+
+ echo $this->getHTML();
+
+ echo "</body></html>\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_' . static::class );
+ wfHttpError( 500, 'Internal Server Error', $this->getText() );
+ } elseif ( self::isCommandLine() ) {
+ $message = $this->getText();
+ // T17602: STDERR may not be available
+ if ( !defined( 'MW_PHPUNIT_TEST' ) && defined( 'STDERR' ) ) {
+ fwrite( STDERR, $message );
+ } else {
+ echo $message;
+ }
+ } 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 );
+ }
+ }
+}
diff --git a/www/wiki/includes/exception/MWExceptionHandler.php b/www/wiki/includes/exception/MWExceptionHandler.php
new file mode 100644
index 00000000..79f0a233
--- /dev/null
+++ b/www/wiki/includes/exception/MWExceptionHandler.php
@@ -0,0 +1,697 @@
+<?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
+ */
+
+use MediaWiki\Logger\LoggerFactory;
+use MediaWiki\MediaWikiServices;
+use Psr\Log\LogLevel;
+use Wikimedia\Rdbms\DBError;
+
+/**
+ * Handler class for MWExceptions
+ * @ingroup Exception
+ */
+class MWExceptionHandler {
+ const CAUGHT_BY_HANDLER = 'mwe_handler'; // error reported by this exception handler
+ const CAUGHT_BY_OTHER = 'other'; // error reported by direct logException() call
+
+ /**
+ * @var string $reservedMemory
+ */
+ protected static $reservedMemory;
+ /**
+ * @var array $fatalErrorTypes
+ */
+ protected static $fatalErrorTypes = [
+ E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR,
+ /* HHVM's FATAL_ERROR level */ 16777217,
+ ];
+ /**
+ * @var bool $handledFatalCallback
+ */
+ protected static $handledFatalCallback = false;
+
+ /**
+ * Install handlers with PHP.
+ */
+ public static function installHandler() {
+ set_exception_handler( 'MWExceptionHandler::handleUncaughtException' );
+ set_error_handler( 'MWExceptionHandler::handleError' );
+
+ // Reserve 16k of memory so we can report OOM fatals
+ self::$reservedMemory = str_repeat( ' ', 16384 );
+ register_shutdown_function( 'MWExceptionHandler::handleFatalError' );
+ }
+
+ /**
+ * Report an exception to the user
+ * @param Exception|Throwable $e
+ */
+ protected static function report( $e ) {
+ try {
+ // Try and show the exception prettily, with the normal skin infrastructure
+ if ( $e instanceof MWException ) {
+ // Delegate to MWException until all subclasses are handled by
+ // MWExceptionRenderer and MWException::report() has been
+ // removed.
+ $e->report();
+ } else {
+ MWExceptionRenderer::output( $e, MWExceptionRenderer::AS_PRETTY );
+ }
+ } catch ( Exception $e2 ) {
+ // Exception occurred from within exception handler
+ // Show a simpler message for the original exception,
+ // don't try to invoke report()
+ MWExceptionRenderer::output( $e, MWExceptionRenderer::AS_RAW, $e2 );
+ }
+ }
+
+ /**
+ * Roll back any open database transactions and log the stack trace of the exception
+ *
+ * This method is used to attempt to recover from exceptions
+ *
+ * @since 1.23
+ * @param Exception|Throwable $e
+ */
+ public static function rollbackMasterChangesAndLog( $e ) {
+ $services = MediaWikiServices::getInstance();
+ if ( !$services->isServiceDisabled( 'DBLoadBalancerFactory' ) ) {
+ // Rollback DBs to avoid transaction notices. This might fail
+ // to rollback some databases due to connection issues or exceptions.
+ // However, any sane DB driver will rollback implicitly anyway.
+ try {
+ $services->getDBLoadBalancerFactory()->rollbackMasterChanges( __METHOD__ );
+ } catch ( DBError $e2 ) {
+ // If the DB is unreacheable, rollback() will throw an error
+ // and the error report() method might need messages from the DB,
+ // which would result in an exception loop. PHP may escalate such
+ // errors to "Exception thrown without a stack frame" fatals, but
+ // it's better to be explicit here.
+ self::logException( $e2, self::CAUGHT_BY_HANDLER );
+ }
+ }
+
+ self::logException( $e, self::CAUGHT_BY_HANDLER );
+ }
+
+ /**
+ * Callback to use with PHP's set_exception_handler.
+ *
+ * @since 1.31
+ * @param Exception|Throwable $e
+ */
+ public static function handleUncaughtException( $e ) {
+ self::handleException( $e );
+
+ // Make sure we don't claim success on exit for CLI scripts (T177414)
+ if ( wfIsCLI() ) {
+ register_shutdown_function(
+ function () {
+ exit( 255 );
+ }
+ );
+ }
+ }
+
+ /**
+ * Exception handler which simulates the appropriate catch() handling:
+ *
+ * try {
+ * ...
+ * } catch ( Exception $e ) {
+ * $e->report();
+ * } catch ( Exception $e ) {
+ * echo $e->__toString();
+ * }
+ *
+ * @since 1.25
+ * @param Exception|Throwable $e
+ */
+ public static function handleException( $e ) {
+ self::rollbackMasterChangesAndLog( $e );
+ self::report( $e );
+ }
+
+ /**
+ * Handler for set_error_handler() callback notifications.
+ *
+ * Receive a callback from the interpreter for a raised error, create an
+ * ErrorException, and log the exception to the 'error' logging
+ * channel(s). If the raised error is a fatal error type (only under HHVM)
+ * delegate to handleFatalError() instead.
+ *
+ * @since 1.25
+ *
+ * @param int $level Error level raised
+ * @param string $message
+ * @param string $file
+ * @param int $line
+ * @return bool
+ *
+ * @see logError()
+ */
+ public static function handleError(
+ $level, $message, $file = null, $line = null
+ ) {
+ global $wgPropagateErrors;
+
+ if ( in_array( $level, self::$fatalErrorTypes ) ) {
+ return call_user_func_array(
+ 'MWExceptionHandler::handleFatalError', func_get_args()
+ );
+ }
+
+ // Map error constant to error name (reverse-engineer PHP error
+ // reporting)
+ switch ( $level ) {
+ case E_RECOVERABLE_ERROR:
+ $levelName = 'Error';
+ $severity = LogLevel::ERROR;
+ break;
+ case E_WARNING:
+ case E_CORE_WARNING:
+ case E_COMPILE_WARNING:
+ case E_USER_WARNING:
+ $levelName = 'Warning';
+ $severity = LogLevel::WARNING;
+ break;
+ case E_NOTICE:
+ case E_USER_NOTICE:
+ $levelName = 'Notice';
+ $severity = LogLevel::INFO;
+ break;
+ case E_STRICT:
+ $levelName = 'Strict Standards';
+ $severity = LogLevel::DEBUG;
+ break;
+ case E_DEPRECATED:
+ case E_USER_DEPRECATED:
+ $levelName = 'Deprecated';
+ $severity = LogLevel::INFO;
+ break;
+ default:
+ $levelName = 'Unknown error';
+ $severity = LogLevel::ERROR;
+ break;
+ }
+
+ $e = new ErrorException( "PHP $levelName: $message", 0, $level, $file, $line );
+ self::logError( $e, 'error', $severity );
+
+ // If $wgPropagateErrors is true return false so PHP shows/logs the error normally.
+ // Ignore $wgPropagateErrors if the error should break execution, or track_errors is set
+ // (which means someone is counting on regular PHP error handling behavior).
+ return !( $wgPropagateErrors || $level == E_RECOVERABLE_ERROR || ini_get( 'track_errors' ) );
+ }
+
+ /**
+ * Dual purpose callback used as both a set_error_handler() callback and
+ * a registered shutdown function. Receive a callback from the interpreter
+ * for a raised error or system shutdown, check for a fatal error, and log
+ * to the 'fatal' logging channel.
+ *
+ * Special handling is included for missing class errors as they may
+ * indicate that the user needs to install 3rd-party libraries via
+ * Composer or other means.
+ *
+ * @since 1.25
+ *
+ * @param int $level Error level raised
+ * @param string $message Error message
+ * @param string $file File that error was raised in
+ * @param int $line Line number error was raised at
+ * @param array $context Active symbol table point of error
+ * @param array $trace Backtrace at point of error (undocumented HHVM
+ * feature)
+ * @return bool Always returns false
+ */
+ public static function handleFatalError(
+ $level = null, $message = null, $file = null, $line = null,
+ $context = null, $trace = null
+ ) {
+ // Free reserved memory so that we have space to process OOM
+ // errors
+ self::$reservedMemory = null;
+
+ if ( $level === null ) {
+ // Called as a shutdown handler, get data from error_get_last()
+ if ( static::$handledFatalCallback ) {
+ // Already called once (probably as an error handler callback
+ // under HHVM) so don't log again.
+ return false;
+ }
+
+ $lastError = error_get_last();
+ if ( $lastError !== null ) {
+ $level = $lastError['type'];
+ $message = $lastError['message'];
+ $file = $lastError['file'];
+ $line = $lastError['line'];
+ } else {
+ $level = 0;
+ $message = '';
+ }
+ }
+
+ if ( !in_array( $level, self::$fatalErrorTypes ) ) {
+ // Only interested in fatal errors, others should have been
+ // handled by MWExceptionHandler::handleError
+ return false;
+ }
+
+ $url = WebRequest::getGlobalRequestURL();
+ $msgParts = [
+ '[{exception_id}] {exception_url} PHP Fatal Error',
+ ( $line || $file ) ? ' from' : '',
+ $line ? " line $line" : '',
+ ( $line && $file ) ? ' of' : '',
+ $file ? " $file" : '',
+ ": $message",
+ ];
+ $msg = implode( '', $msgParts );
+
+ // Look at message to see if this is a class not found failure
+ // HHVM: Class undefined: foo
+ // PHP5: Class 'foo' not found
+ if ( preg_match( "/Class (undefined: \w+|'\w+' not found)/", $message ) ) {
+ // phpcs:disable Generic.Files.LineLength
+ $msg = <<<TXT
+{$msg}
+
+MediaWiki or an installed extension requires this class but it is not embedded directly in MediaWiki's git repository and must be installed separately by the end user.
+
+Please see <a href="https://www.mediawiki.org/wiki/Download_from_Git#Fetch_external_libraries">mediawiki.org</a> for help on installing the required components.
+TXT;
+ // phpcs:enable
+ }
+
+ // We can't just create an exception and log it as it is likely that
+ // the interpreter has unwound the stack already. If that is true the
+ // stacktrace we would get would be functionally empty. If however we
+ // have been called as an error handler callback *and* HHVM is in use
+ // we will have been provided with a useful stacktrace that we can
+ // log.
+ $trace = $trace ?: debug_backtrace();
+ $logger = LoggerFactory::getInstance( 'fatal' );
+ $logger->error( $msg, [
+ 'fatal_exception' => [
+ 'class' => ErrorException::class,
+ 'message' => "PHP Fatal Error: {$message}",
+ 'code' => $level,
+ 'file' => $file,
+ 'line' => $line,
+ 'trace' => self::prettyPrintTrace( self::redactTrace( $trace ) ),
+ ],
+ 'exception_id' => WebRequest::getRequestId(),
+ 'exception_url' => $url,
+ 'caught_by' => self::CAUGHT_BY_HANDLER
+ ] );
+
+ // Remember call so we don't double process via HHVM's fatal
+ // notifications and the shutdown hook behavior
+ static::$handledFatalCallback = true;
+ return false;
+ }
+
+ /**
+ * 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|Throwable $e
+ * @return string
+ * @see prettyPrintTrace()
+ */
+ public static function getRedactedTraceAsString( $e ) {
+ return self::prettyPrintTrace( self::getRedactedTrace( $e ) );
+ }
+
+ /**
+ * Generate a string representation of a stacktrace.
+ *
+ * @param array $trace
+ * @param string $pad Constant padding to add to each line of trace
+ * @return string
+ * @since 1.26
+ */
+ public static function prettyPrintTrace( array $trace, $pad = '' ) {
+ $text = '';
+
+ $level = 0;
+ foreach ( $trace as $level => $frame ) {
+ if ( isset( $frame['file'] ) && isset( $frame['line'] ) ) {
+ $text .= "{$pad}#{$level} {$frame['file']}({$frame['line']}): ";
+ } else {
+ // 'file' and 'line' are unset for calls via call_user_func
+ // (T57634) This matches behaviour of
+ // Exception::getTraceAsString to instead display "[internal
+ // function]".
+ $text .= "{$pad}#{$level} [internal function]: ";
+ }
+
+ if ( isset( $frame['class'] ) && isset( $frame['type'] ) && isset( $frame['function'] ) ) {
+ $text .= $frame['class'] . $frame['type'] . $frame['function'];
+ } elseif ( isset( $frame['function'] ) ) {
+ $text .= $frame['function'];
+ } else {
+ $text .= 'NO_FUNCTION_GIVEN';
+ }
+
+ if ( isset( $frame['args'] ) ) {
+ $text .= '(' . implode( ', ', $frame['args'] ) . ")\n";
+ } else {
+ $text .= "()\n";
+ }
+ }
+
+ $level = $level + 1;
+ $text .= "{$pad}#{$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|Throwable $e
+ * @return array
+ */
+ public static function getRedactedTrace( $e ) {
+ return static::redactTrace( $e->getTrace() );
+ }
+
+ /**
+ * Redact a stacktrace generated by Exception::getTrace(),
+ * debug_backtrace() or similar means. 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.26
+ * @param array $trace Stacktrace
+ * @return array Stacktrace with arugment values converted to data types
+ */
+ public static function redactTrace( array $trace ) {
+ 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;
+ }, $trace );
+ }
+
+ /**
+ * Get the ID for this exception.
+ *
+ * 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
+ * @deprecated since 1.27: Exception IDs are synonymous with request IDs.
+ * @param Exception|Throwable $e
+ * @return string
+ */
+ public static function getLogId( $e ) {
+ wfDeprecated( __METHOD__, '1.27' );
+ return WebRequest::getRequestId();
+ }
+
+ /**
+ * If the exception occurred in the course of responding to a request,
+ * returns the requested URL. Otherwise, returns false.
+ *
+ * @since 1.23
+ * @return string|false
+ */
+ public static function getURL() {
+ global $wgRequest;
+ if ( !isset( $wgRequest ) || $wgRequest instanceof FauxRequest ) {
+ return false;
+ }
+ return $wgRequest->getRequestURL();
+ }
+
+ /**
+ * Get a message formatting the exception message and its origin.
+ *
+ * @since 1.22
+ * @param Exception|Throwable $e
+ * @return string
+ */
+ public static function getLogMessage( $e ) {
+ $id = WebRequest::getRequestId();
+ $type = get_class( $e );
+ $file = $e->getFile();
+ $line = $e->getLine();
+ $message = $e->getMessage();
+ $url = self::getURL() ?: '[no req]';
+
+ return "[$id] $url $type from line $line of $file: $message";
+ }
+
+ /**
+ * Get a normalised message for formatting with PSR-3 log event context.
+ *
+ * Must be used together with `getLogContext()` to be useful.
+ *
+ * @since 1.30
+ * @param Exception|Throwable $e
+ * @return string
+ */
+ public static function getLogNormalMessage( $e ) {
+ $type = get_class( $e );
+ $file = $e->getFile();
+ $line = $e->getLine();
+ $message = $e->getMessage();
+
+ return "[{exception_id}] {exception_url} $type from line $line of $file: $message";
+ }
+
+ /**
+ * @param Exception|Throwable $e
+ * @return string
+ */
+ public static function getPublicLogMessage( $e ) {
+ $reqId = WebRequest::getRequestId();
+ $type = get_class( $e );
+ return '[' . $reqId . '] '
+ . gmdate( 'Y-m-d H:i:s' ) . ': '
+ . 'Fatal exception of type "' . $type . '"';
+ }
+
+ /**
+ * Get a PSR-3 log event context from an Exception.
+ *
+ * Creates a structured array containing information about the provided
+ * exception that can be used to augment a log message sent to a PSR-3
+ * logger.
+ *
+ * @param Exception|Throwable $e
+ * @param string $catcher CAUGHT_BY_* class constant indicating what caught the error
+ * @return array
+ */
+ public static function getLogContext( $e, $catcher = self::CAUGHT_BY_OTHER ) {
+ return [
+ 'exception' => $e,
+ 'exception_id' => WebRequest::getRequestId(),
+ 'exception_url' => self::getURL() ?: '[no req]',
+ 'caught_by' => $catcher
+ ];
+ }
+
+ /**
+ * Get a structured representation of an Exception.
+ *
+ * Returns an array of structured data (class, message, code, file,
+ * backtrace) derived from the given exception. The backtrace information
+ * will be redacted as per getRedactedTraceAsArray().
+ *
+ * @param Exception|Throwable $e
+ * @param string $catcher CAUGHT_BY_* class constant indicating what caught the error
+ * @return array
+ * @since 1.26
+ */
+ public static function getStructuredExceptionData( $e, $catcher = self::CAUGHT_BY_OTHER ) {
+ global $wgLogExceptionBacktrace;
+
+ $data = [
+ 'id' => WebRequest::getRequestId(),
+ 'type' => get_class( $e ),
+ 'file' => $e->getFile(),
+ 'line' => $e->getLine(),
+ 'message' => $e->getMessage(),
+ 'code' => $e->getCode(),
+ 'url' => self::getURL() ?: null,
+ 'caught_by' => $catcher
+ ];
+
+ if ( $e instanceof ErrorException &&
+ ( error_reporting() & $e->getSeverity() ) === 0
+ ) {
+ // Flag surpressed errors
+ $data['suppressed'] = true;
+ }
+
+ if ( $wgLogExceptionBacktrace ) {
+ $data['backtrace'] = self::getRedactedTrace( $e );
+ }
+
+ $previous = $e->getPrevious();
+ if ( $previous !== null ) {
+ $data['previous'] = self::getStructuredExceptionData( $previous, $catcher );
+ }
+
+ return $data;
+ }
+
+ /**
+ * 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",
+ * "type": "MWException",
+ * "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",
+ * "type": "MWException",
+ * "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|Throwable $e
+ * @param bool $pretty Add non-significant whitespace to improve readability (default: false).
+ * @param int $escaping Bitfield consisting of FormatJson::.*_OK class constants.
+ * @param string $catcher CAUGHT_BY_* class constant indicating what caught the error
+ * @return string|false JSON string if successful; false upon failure
+ */
+ public static function jsonSerializeException(
+ $e, $pretty = false, $escaping = 0, $catcher = self::CAUGHT_BY_OTHER
+ ) {
+ return FormatJson::encode(
+ self::getStructuredExceptionData( $e, $catcher ),
+ $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 exceptions or exceptions from other libraries.
+ *
+ * @since 1.22
+ * @param Exception|Throwable $e
+ * @param string $catcher CAUGHT_BY_* class constant indicating what caught the error
+ */
+ public static function logException( $e, $catcher = self::CAUGHT_BY_OTHER ) {
+ if ( !( $e instanceof MWException ) || $e->isLoggable() ) {
+ $logger = LoggerFactory::getInstance( 'exception' );
+ $logger->error(
+ self::getLogNormalMessage( $e ),
+ self::getLogContext( $e, $catcher )
+ );
+
+ $json = self::jsonSerializeException( $e, false, FormatJson::ALL_OK, $catcher );
+ if ( $json !== false ) {
+ $logger = LoggerFactory::getInstance( 'exception-json' );
+ $logger->error( $json, [ 'private' => true ] );
+ }
+
+ Hooks::run( 'LogException', [ $e, false ] );
+ }
+ }
+
+ /**
+ * Log an exception that wasn't thrown but made to wrap an error.
+ *
+ * @since 1.25
+ * @param ErrorException $e
+ * @param string $channel
+ * @param string $level
+ */
+ protected static function logError(
+ ErrorException $e, $channel, $level = LogLevel::ERROR
+ ) {
+ $catcher = self::CAUGHT_BY_HANDLER;
+ // The set_error_handler callback is independent from error_reporting.
+ // Filter out unwanted errors manually (e.g. when
+ // Wikimedia\suppressWarnings is active).
+ $suppressed = ( error_reporting() & $e->getSeverity() ) === 0;
+ if ( !$suppressed ) {
+ $logger = LoggerFactory::getInstance( $channel );
+ $logger->log(
+ $level,
+ self::getLogNormalMessage( $e ),
+ self::getLogContext( $e, $catcher )
+ );
+ }
+
+ // Include all errors in the json log (surpressed errors will be flagged)
+ $json = self::jsonSerializeException( $e, false, FormatJson::ALL_OK, $catcher );
+ if ( $json !== false ) {
+ $logger = LoggerFactory::getInstance( "{$channel}-json" );
+ $logger->log( $level, $json, [ 'private' => true ] );
+ }
+
+ Hooks::run( 'LogException', [ $e, $suppressed ] );
+ }
+}
diff --git a/www/wiki/includes/exception/MWExceptionRenderer.php b/www/wiki/includes/exception/MWExceptionRenderer.php
new file mode 100644
index 00000000..5d750365
--- /dev/null
+++ b/www/wiki/includes/exception/MWExceptionRenderer.php
@@ -0,0 +1,338 @@
+<?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
+ */
+
+use Wikimedia\Rdbms\DBConnectionError;
+use Wikimedia\Rdbms\DBError;
+use Wikimedia\Rdbms\DBReadOnlyError;
+use Wikimedia\Rdbms\DBExpectedError;
+
+/**
+ * Class to expose exceptions to the client (API bots, users, admins using CLI scripts)
+ * @since 1.28
+ */
+class MWExceptionRenderer {
+ const AS_RAW = 1; // show as text
+ const AS_PRETTY = 2; // show as HTML
+
+ /**
+ * @param Exception|Throwable $e Original exception
+ * @param int $mode MWExceptionExposer::AS_* constant
+ * @param Exception|Throwable|null $eNew New exception from attempting to show the first
+ */
+ 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 );
+ self::header( "Content-Type: $wgMimeType; charset=utf-8" );
+ if ( $e instanceof DBConnectionError ) {
+ self::reportOutageHTML( $e );
+ } else {
+ self::reportHTML( $e );
+ }
+ } else {
+ self::statusHeader( 500 );
+ self::header( "Content-Type: $wgMimeType; charset=utf-8" );
+ 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::class ) {
+ return false;
+ }
+ }
+
+ // Don't even bother with OutputPage if there's no Title context set,
+ // (e.g. we're in RL code on load.php) - the Skin system (and probably
+ // most of MediaWiki) won't work.
+
+ return (
+ !empty( $GLOBALS['wgFullyInitialised'] ) &&
+ !empty( $GLOBALS['wgOut'] ) &&
+ RequestContext::getMain()->getTitle() &&
+ !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( Html::element( 'p', [], Message::newFromSpecifier( $e )->text() ) );
+ }
+ $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";
+ }
+ }
+
+ /**
+ * If $wgShowExceptionDetails is true, return a HTML message with a
+ * backtrace to the error, otherwise show a message to ask to set it to true
+ * to show that information.
+ *
+ * @param Exception|Throwable $e
+ * @return string Html to output
+ */
+ 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\">" .
+ htmlspecialchars(
+ '[' . $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;
+ }
+
+ /**
+ * Get a message from i18n
+ *
+ * @param string $key Message name
+ * @param string $fallback Default message if the message cache can't be
+ * called by the exception
+ * The function also has other parameters that are arguments for the message
+ * @return string Message with arguments replaced
+ */
+ private static function msg( $key, $fallback /*[, params...] */ ) {
+ $args = array_slice( func_get_args(), 2 );
+ try {
+ return wfMessage( $key, $args )->text();
+ } catch ( Exception $e ) {
+ return wfMsgReplaceArgs( $fallback, $args );
+ }
+ }
+
+ /**
+ * @param Exception|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 ) . "\n";
+ }
+ }
+
+ /**
+ * @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.";
+ }
+
+ /**
+ * @return bool
+ */
+ private static function isCommandLine() {
+ return !empty( $GLOBALS['wgCommandLineMode'] );
+ }
+
+ /**
+ * @param string $header
+ */
+ private static function header( $header ) {
+ if ( !headers_sent() ) {
+ header( $header );
+ }
+ }
+
+ /**
+ * @param int $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, $wgSitename;
+
+ $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' ], $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 = "<!DOCTYPE html>\n" .
+ '<html><head>' .
+ '<title>' .
+ htmlspecialchars( $wgSitename ) .
+ '</title>' .
+ '<style>body { font-family: sans-serif; margin: 0; padding: 0.5em 2em; }</style>' .
+ "</head><body><h1>$sorry</h1><p>$again</p><p><small>$info</small></p>";
+
+ if ( $wgShowDBErrorBacktrace ) {
+ $html .= '<p>Backtrace:</p><pre>' .
+ htmlspecialchars( $e->getTraceAsString() ) . '</pre>';
+ }
+
+ $html .= '</body></html>';
+ echo $html;
+ }
+}
diff --git a/www/wiki/includes/exception/MWUnknownContentModelException.php b/www/wiki/includes/exception/MWUnknownContentModelException.php
new file mode 100644
index 00000000..df7111ac
--- /dev/null
+++ b/www/wiki/includes/exception/MWUnknownContentModelException.php
@@ -0,0 +1,25 @@
+<?php
+/**
+ * Exception thrown when an unregistered content model is requested. This error
+ * can be triggered by user input, so a separate exception class is provided so
+ * callers can substitute a context-specific, internationalised error message.
+ *
+ * @ingroup Content
+ * @since 1.27
+ */
+class MWUnknownContentModelException extends MWException {
+ /** @var string The name of the unknown content model */
+ private $modelId;
+
+ /** @param string $modelId */
+ function __construct( $modelId ) {
+ parent::__construct( "The content model '$modelId' is not registered on this wiki.\n" .
+ 'See https://www.mediawiki.org/wiki/Content_handlers to find out which extensions ' .
+ 'handle this content model.' );
+ $this->modelId = $modelId;
+ }
+ /** @return string */
+ public function getModelId() {
+ return $this->modelId;
+ }
+}
diff --git a/www/wiki/includes/exception/PermissionsError.php b/www/wiki/includes/exception/PermissionsError.php
new file mode 100644
index 00000000..cc69a762
--- /dev/null
+++ b/www/wiki/includes/exception/PermissionsError.php
@@ -0,0 +1,72 @@
+<?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
+ */
+
+/**
+ * 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;
+
+ /**
+ * @param string|null $permission A permission name or null if unknown
+ * @param array $errors Error message keys or [key, param...] arrays; must not be empty if
+ * $permission is null
+ * @throws \InvalidArgumentException
+ */
+ public function __construct( $permission, $errors = [] ) {
+ global $wgLang;
+
+ if ( $permission === null && !$errors ) {
+ throw new \InvalidArgumentException( __METHOD__ .
+ ': $permission and $errors cannot both be empty' );
+ }
+
+ $this->permission = $permission;
+
+ if ( !count( $errors ) ) {
+ $groups = [];
+ foreach ( User::getGroupsWithPermission( $this->permission ) as $group ) {
+ $groups[] = UserGroupMembership::getLink( $group, RequestContext::getMain(), 'wiki' );
+ }
+
+ if ( $groups ) {
+ $errors[] = [ 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) ];
+ } else {
+ $errors[] = [ 'badaccess-group0' ];
+ }
+ }
+
+ $this->errors = $errors;
+
+ // Give the parent class something to work with
+ parent::__construct( 'permissionserrors', Message::newFromSpecifier( $errors[0] ) );
+ }
+
+ public function report() {
+ global $wgOut;
+
+ $wgOut->showPermissionsErrorPage( $this->errors, $this->permission );
+ $wgOut->output();
+ }
+}
diff --git a/www/wiki/includes/exception/ProcOpenError.php b/www/wiki/includes/exception/ProcOpenError.php
new file mode 100644
index 00000000..f00bcd4b
--- /dev/null
+++ b/www/wiki/includes/exception/ProcOpenError.php
@@ -0,0 +1,29 @@
+<?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
+ */
+
+namespace MediaWiki;
+
+use Exception;
+
+class ProcOpenError extends Exception {
+ public function __construct() {
+ parent::__construct( 'proc_open() returned error!' );
+ }
+}
diff --git a/www/wiki/includes/exception/ReadOnlyError.php b/www/wiki/includes/exception/ReadOnlyError.php
new file mode 100644
index 00000000..de42f056
--- /dev/null
+++ b/www/wiki/includes/exception/ReadOnlyError.php
@@ -0,0 +1,36 @@
+<?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
+ */
+
+/**
+ * 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() ?: []
+ );
+ }
+}
diff --git a/www/wiki/includes/exception/ShellDisabledError.php b/www/wiki/includes/exception/ShellDisabledError.php
new file mode 100644
index 00000000..203b58dc
--- /dev/null
+++ b/www/wiki/includes/exception/ShellDisabledError.php
@@ -0,0 +1,32 @@
+<?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
+ */
+
+namespace MediaWiki;
+
+use Exception;
+
+/**
+ * @since 1.30
+ */
+class ShellDisabledError extends Exception {
+ public function __construct() {
+ parent::__construct( 'Unable to run external programs, proc_open() is disabled' );
+ }
+}
diff --git a/www/wiki/includes/exception/ThrottledError.php b/www/wiki/includes/exception/ThrottledError.php
new file mode 100644
index 00000000..bec0d904
--- /dev/null
+++ b/www/wiki/includes/exception/ThrottledError.php
@@ -0,0 +1,40 @@
+<?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
+ */
+
+/**
+ * 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( 429 );
+ parent::report();
+ }
+}
diff --git a/www/wiki/includes/exception/UserBlockedError.php b/www/wiki/includes/exception/UserBlockedError.php
new file mode 100644
index 00000000..9d19f8b6
--- /dev/null
+++ b/www/wiki/includes/exception/UserBlockedError.php
@@ -0,0 +1,33 @@
+<?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
+ */
+
+/**
+ * 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 );
+ }
+}
diff --git a/www/wiki/includes/exception/UserNotLoggedIn.php b/www/wiki/includes/exception/UserNotLoggedIn.php
new file mode 100644
index 00000000..6086d559
--- /dev/null
+++ b/www/wiki/includes/exception/UserNotLoggedIn.php
@@ -0,0 +1,104 @@
+<?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
+ */
+
+/**
+ * Redirect a user to the login page
+ *
+ * This is essentially an ErrorPageError exception which by default uses the
+ * 'exception-nologin' as a title and 'exception-nologin-text' for the message.
+ *
+ * @note In order for this exception to redirect, the error message passed to the
+ * constructor has to be explicitly added to LoginHelper::validErrorMessages or with
+ * the LoginFormValidErrorMessages hook. Otherwise, the user will just be shown the message
+ * rather than redirected.
+ *
+ * @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
+ *
+ * @see T39627
+ * @since 1.20
+ * @ingroup Exception
+ */
+class UserNotLoggedIn extends ErrorPageError {
+
+ /**
+ * @note The value of the $reasonMsg parameter must be put into LoginForm::validErrorMessages or
+ * set with the LoginFormValidErrorMessages Hook.
+ * if you want the user to be automatically redirected to the login form.
+ *
+ * @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: []
+ */
+ public function __construct(
+ $reasonMsg = 'exception-nologin-text',
+ $titleMsg = 'exception-nologin',
+ $params = []
+ ) {
+ parent::__construct( $titleMsg, $reasonMsg, $params );
+ }
+
+ /**
+ * Redirect to Special:Userlogin if the specified message is compatible. Otherwise,
+ * show an error page as usual.
+ */
+ public function report() {
+ // If an unsupported message is used, don't try redirecting to Special:Userlogin,
+ // since the message may not be compatible.
+ if ( !in_array( $this->msg, LoginHelper::getValidErrorMessages() ) ) {
+ parent::report();
+ }
+
+ // Message is valid. Redirec to Special:Userlogin
+
+ $context = RequestContext::getMain();
+
+ $output = $context->getOutput();
+ $query = $context->getRequest()->getValues();
+ // Title will be overridden by returnto
+ unset( $query['title'] );
+ // Redirect to Special:Userlogin
+ $output->redirect( SpecialPage::getTitleFor( 'Userlogin' )->getFullURL( [
+ // Return to this page when the user logs in
+ 'returnto' => $context->getTitle()->getFullText(),
+ 'returntoquery' => wfArrayToCgi( $query ),
+ 'warning' => $this->msg,
+ ] ) );
+
+ $output->output();
+ }
+}