| Current File : /home/jvzmxxx/wiki/extensions/Wikibase/repo/includes/Api/ApiErrorReporter.php |
<?php
namespace Wikibase\Repo\Api;
use ApiBase;
use ApiResult;
use Exception;
use InvalidArgumentException;
use Language;
use LogicException;
use Message;
use Status;
use UsageException;
use Wikibase\Repo\Localizer\ExceptionLocalizer;
/**
* ApiErrorReporter is a component for API modules that handles
* error reporting. It supports localization of error messages.
*
* @since 0.5
*
* @license GPL-2.0+
* @author Daniel Kinzler
*/
class ApiErrorReporter {
/**
* @var ApiBase
*/
private $apiModule;
/**
* @var ExceptionLocalizer
*/
private $localizer;
/**
* @var Language
*/
private $language;
/**
* @param ApiBase $apiModule the API module for collaboration
* @param ExceptionLocalizer $localizer
* @param Language $language
*/
public function __construct(
ApiBase $apiModule,
ExceptionLocalizer $localizer,
Language $language
) {
$this->apiModule = $apiModule;
$this->localizer = $localizer;
$this->language = $language;
}
/**
* Reports any warnings in the Status object on the warnings section
* of the result.
*
* @param Status $status
*/
public function reportStatusWarnings( Status $status ) {
$warnings = $status->getWarningsArray();
if ( !empty( $warnings ) ) {
$warnings = $this->convertMessagesToResult( $warnings );
$this->setWarning( 'messages', $warnings );
}
}
/**
* Set warning section for this module.
*
* @param string $key
* @param string|array $warningData Warning message
*/
private function setWarning( $key, $warningData ) {
$result = $this->apiModule->getResult();
$moduleName = $this->apiModule->getModuleName();
$result->addValue(
array( 'warnings', $moduleName ),
$key,
$warningData,
ApiResult::NO_SIZE_CHECK
);
}
/**
* Aborts the request with an error based on the given (fatal) Status.
* This is intended as an alternative for ApiBase::dieUsage().
*
* If possible, a localized error message based on the exception is
* included in the error sent to the client. Localization of errors
* is attempted using the ExceptionLocalizer service provided to the
* constructor. If that fails, dieUsage() is called, which in turn
* attempts localization based on the error code.
*
* @see ApiBase::dieUsage()
*
* @param Status $status The status to report. $status->getMessage() will be used
* to generate the error's free form description.
* @param string $errorCode A code identifying the error.
* @param int $httpRespCode The HTTP error code to send to the client
* @param array|null $extradata Any extra data to include in the error report
*
* @throws UsageException
* @throws LogicException
*/
public function dieStatus( Status $status, $errorCode, $httpRespCode = 0, $extradata = array() ) {
if ( $status->isOK() ) {
throw new InvalidArgumentException( 'called dieStatus() with a non-fatal Status!' );
}
$this->addStatusToResult( $status, $extradata );
//XXX: when to prefer $statusCode over $errorCode?
list( , $description ) = $this->apiModule->getErrorFromStatus( $status );
$this->throwUsageException( $description, $errorCode, $httpRespCode, $extradata );
throw new LogicException( 'UsageException not thrown' );
}
/**
* Aborts the request with an error based on the given Exception.
* This is intended as an alternative for ApiBase::dieUsage().
*
* If possible, a localized error message based on the exception is
* included in the error sent to the client. Localization of errors
* is attempted using the ExceptionLocalizer service provided to the
* constructor. If that fails, dieUsage() is called, which in turn
* attempts localization based on the error code.
*
* @see ApiBase::dieUsage()
*
* @param Exception $ex The exception to report. $ex->getMessage() will be used as the error's
* free form description.
* @param string $errorCode A code identifying the error.
* @param int $httpRespCode The HTTP error code to send to the client
* @param array|null $extradata Any extra data to include in the error report
*
* @throws UsageException
* @throws LogicException
*/
public function dieException( Exception $ex, $errorCode, $httpRespCode = 0, $extradata = array() ) {
if ( $this->localizer->hasExceptionMessage( $ex ) ) {
$message = $this->localizer->getExceptionMessage( $ex );
$key = $message->getKey();
// NOTE: Ignore generic error messages, rely on the code instead!
// XXX: No better way to do this?
if ( $key !== 'wikibase-error-unexpected' ) {
$this->dieMessageObject( $message, $errorCode, $httpRespCode, $extradata );
}
}
$this->dieError( $ex->getMessage(), $errorCode, $httpRespCode, $extradata );
throw new LogicException( 'UsageException not thrown' );
}
/**
* Aborts the request with an error message derived from the error code.
*
* @param string $errorCode A code identifying the error.
* @param string [$param,...] Parameters for the Message.
*
* @throws UsageException
* @throws LogicException
*/
public function dieMessage( $errorCode /*...*/ ) {
$messageName = "wikibase-api-$errorCode";
$params = func_get_args();
array_shift( $params );
$message = wfMessage( $messageName, $params );
if ( !$message->exists() ) {
// TODO: log warning
// TODO: replace with generic message
}
$this->dieMessageObject( $message, $errorCode );
throw new LogicException( 'UsageException not thrown' );
}
/**
* Aborts the request with an error message. The given message is included in
* the error's extra data.
*
* @see ApiBase::dieUsage()
*
* @param Message $message The error message. Will be used to generate the free form description
* of the error (as plain text in the content language) and included in the extra data (as
* HTML in the user's language, and as a data structure including the message key and
* parameters).
* @param string $errorCode A code identifying the error.
* @param int $httpRespCode The HTTP error code to send to the client
* @param array|null $extradata Any extra data to include in the error report
*
* @throws UsageException
* @throws LogicException
*/
private function dieMessageObject( Message $message, $errorCode, $httpRespCode = 0, $extradata = array() ) {
$description = $message->inLanguage( 'en' )->useDatabase( false )->plain();
$this->addMessageToResult( $message, $extradata );
$this->throwUsageException( $description, $errorCode, $httpRespCode, $extradata );
throw new LogicException( 'UsageException not thrown' );
}
/**
* Aborts the request with an error code. This is intended as a drop-in
* replacement for ApiBase::dieUsage().
*
* Localization of the error code is attempted by looking up a message key
* constructed using the given code in "wikibase-api-$errorCode". If such a message
* exists, it is included in the error's extra data.
*
* @see ApiBase::dieUsage()
*
* @param string $description An english, plain text description of the errror,
* for use in logs.
* @param string $errorCode A code identifying the error
* @param int $httpRespCode The HTTP error code to send to the client
* @param array|null $extradata Any extra data to include in the error report
*
* @throws UsageException
* @throws LogicException
*/
public function dieError( $description, $errorCode, $httpRespCode = 0, $extradata = array() ) {
//TODO: try a reverse lookup in ApiBase::$messageMap
$messageKey = "wikibase-api-$errorCode";
$message = wfMessage( $messageKey );
if ( $message->exists() ) {
$this->addMessageToResult( $message, $extradata );
$text = $message->inLanguage( 'en' )->useDatabase( false )->plain();
if ( $description == '' ) {
$description = $text;
} else {
$description = "$text ($description)";
}
}
$this->throwUsageException( $description, $errorCode, $httpRespCode, $extradata );
throw new LogicException( 'UsageException not thrown' );
}
/**
* Throws a UsageException by calling ApiBase::dieUsage().
*
* @see ApiBase::dieUsage()
*
* @param string $description
* @param string $errorCode
* @param int $httpRespCode
* @param null|array $extradata
*
* @throws UsageException
* @throws LogicException
*/
private function throwUsageException( $description, $errorCode, $httpRespCode = 0, $extradata = null ) {
$this->apiModule->getStats()->increment( 'wikibase.repo.api.errors.total' );
$this->apiModule->getMain()->dieUsage( $description, $errorCode, $httpRespCode, $extradata );
throw new LogicException( 'UsageException not thrown' );
}
/**
* Add the given message to the $data array, for use in an error report.
*
* @param Message $message
* @param array|null &$data
*
* @throws InvalidArgumentException
*/
public function addMessageToResult( Message $message, &$data ) {
if ( $data === null ) {
$data = array();
}
if ( !is_array( $data ) ) {
throw new InvalidArgumentException( '$extradata must be an array' );
}
$messageData = $this->convertMessageToResult( $message );
$messageList = isset( $data['messages'] ) ? $data['messages'] : array();
ApiResult::setIndexedTagName( $messageList, 'message' );
$messageList[] = $messageData;
ApiResult::setValue( $data, 'messages', $messageList, ApiResult::OVERRIDE );
}
/**
* Add the messages from the given Status object to the $data array,
* for use in an error report.
*
* @param Status $status
* @param array|null &$data
*
* @throws InvalidArgumentException
*/
public function addStatusToResult( Status $status, &$data ) {
$messageSpecs = $status->getErrorsArray();
$messages = $this->convertToMessageList( $messageSpecs );
foreach ( $messages as $message ) {
$this->addMessageToResult( $message, $data );
}
}
/**
* Utility method for compiling a list of messages into a form suitable for use
* in an API result structure.
*
* The $errors parameters is a list of (error) messages. Each entry in that array
* represents on message; the message can be represented as:
*
* * a message key, as a string
* * an indexed array with the message key as the first element, and the remaining elements
* acting as message parameters
* * an associative array with the following fields:
* - message: the message key (as a string); may also be a Message object, see below for that.
* - params: a list of parameters (optional)
* - type: the type of message (warning or error) (optional)
* - html: an HTML rendering of the message (optional)
* * an associative array like above, but containing a Message object in the 'message' field.
* In that case, the 'params' field is ignored and the parameter list is taken from the
* Message object.
*
* This provides support for message lists coming from Status::getErrorsByType() as well as
* Title::getUserPermissionsErrors() etc.
*
* @param array $messageSpecs a list of errors, as returned by Status::getErrorsByType()
* or Title::getUserPermissionsErrors()
*
* @return array a result structure containing the messages from $errors as well as what
* was already present in the $messages parameter.
*/
private function convertMessagesToResult( array $messageSpecs ) {
$result = array();
foreach ( $messageSpecs as $message ) {
$type = null;
if ( !( $message instanceof Message ) ) {
if ( is_array( $message ) && isset( $message['type'] ) ) {
$type = $message['type'];
}
$message = $this->convertToMessage( $message );
}
if ( !$message ) {
continue;
}
$row = $this->convertMessageToResult( $message );
if ( $type !== null ) {
ApiResult::setValue( $row, 'type', $type );
}
$result[] = $row;
}
ApiResult::setIndexedTagName( $result, 'message' );
return $result;
}
/**
* Utility method for building a list of Message objects from
* an array of message specs.
*
* @see convertToMessage()
*
* @param array $messageSpecs a list of errors, as returned by Status::getErrorsByType()
* or Title::getUserPermissionsErrors().
*
* @return array a result structure containing the messages from $errors as well as what
* was already present in the $messages parameter.
*/
private function convertToMessageList( array $messageSpecs ) {
$messages = array();
foreach ( $messageSpecs as $message ) {
if ( !( $message instanceof Message ) ) {
$message = $this->convertToMessage( $message );
}
if ( !$message ) {
continue;
}
$messages[] = $message;
}
return $messages;
}
/**
* Constructs a result structure from the given Message
*
* @param Message $message
*
* @return array
*/
private function convertMessageToResult( Message $message ) {
$name = $message->getKey();
$params = $message->getParams();
$row = array();
ApiResult::setValue( $row, 'name', $name );
ApiResult::setValue( $row, 'parameters', $params );
ApiResult::setIndexedTagName( $row['parameters'], 'parameter' );
$html = $message->inLanguage( $this->language )->useDatabase( true )->parse();
ApiResult::setValue( $row, 'html', $html );
$row[ApiResult::META_BC_SUBELEMENTS][] = 'html';
return $row;
}
/**
* Utility function for converting a message specified as a string or array
* to a Message object. Returns null if this is not possible.
*
* The formats supported by this method are the formats used by the Status class as well as
* the one used by Title::getUserPermissionsErrors().
*
* The spec may be structured as follows:
* * a message key, as a string
* * an indexed array with the message key as the first element, and the remaining elements
* acting as message parameters
* * an associative array with the following fields:
* - message: the message key (as a string); may also be a Message object, see below for that.
* - params: a list of parameters (optional)
* - type: the type of message (warning or error) (optional)
* - html: an HTML rendering of the message (optional)
* * an associative array like above, but containing a Message object in the 'message' field.
* In that case, the 'params' field is ignored and the parameter list is taken from the
* Message object.
*
* @param string|array $messageSpec The message spec.
*
* @return Message|null
*/
private function convertToMessage( $messageSpec ) {
$name = null;
$params = null;
if ( is_string( $messageSpec ) ) {
// it's a plain string containing a message key
$name = $messageSpec;
} elseif ( is_array( $messageSpec ) ) {
if ( isset( $messageSpec[0] ) ) {
// it's an indexed array, the first entriy is the message key, the rest are paramters
$name = $messageSpec[0];
$params = array_slice( $messageSpec, 1 );
} else {
// it's an assoc array, find message key and params in fields.
$params = isset( $messageSpec['params'] ) ? $messageSpec['params'] : null;
if ( isset( $messageSpec['message'] ) ) {
if ( $messageSpec['message'] instanceof Message ) {
// message object found, done.
return $messageSpec['message'];
} else {
// plain key and param list
$name = strval( $messageSpec['message'] );
}
}
}
}
if ( $name !== null ) {
$message = wfMessage( $name );
if ( !empty( $params ) ) {
$message->params( $params );
}
return $message;
}
return null;
}
}