public function execute() {
// Check whether upload is enabled
if ( !UploadBase::isEnabled() ) {
- $this->dieUsageMsg( 'uploaddisabled' );
+ $this->dieWithError( 'uploaddisabled' );
}
$user = $this->getUser();
if ( !$this->selectUploadModule() ) {
return; // not a true upload, but a status request or similar
} elseif ( !isset( $this->mUpload ) ) {
- $this->dieUsage( 'No upload module set', 'nomodule' );
+ $this->dieDebug( __METHOD__, 'No upload module set' );
}
} catch ( UploadStashException $e ) { // XXX: don't spam exception log
- list( $msg, $code ) = $this->handleStashException( get_class( $e ), $e->getMessage() );
- $this->dieUsage( $msg, $code );
+ $this->dieStatus( $this->handleStashException( $e ) );
}
// First check permission to upload
/** @var $status Status */
$status = $this->mUpload->fetchFile();
if ( !$status->isGood() ) {
- $errors = $status->getErrorsArray();
- $error = array_shift( $errors[0] );
- $this->dieUsage( 'Error fetching file from remote source', $error, 0, $errors[0] );
+ $this->dieStatus( $status );
}
// Check if the uploaded file is sane
if ( $this->mParams['chunk'] ) {
$maxSize = UploadBase::getMaxUploadSize();
if ( $this->mParams['filesize'] > $maxSize ) {
- $this->dieUsage( 'The file you submitted was too large', 'file-too-large' );
+ $this->dieWithError( 'file-too-large' );
}
if ( !$this->mUpload->getTitle() ) {
- $this->dieUsage( 'Invalid file title supplied', 'internal-error' );
+ $this->dieWithError( 'illegal-filename' );
}
} elseif ( $this->mParams['async'] && $this->mParams['filekey'] ) {
// defer verification to background process
if ( !$this->mParams['stash'] ) {
$permErrors = $this->mUpload->verifyTitlePermissions( $user );
if ( $permErrors !== true ) {
- $this->dieRecoverableError( $permErrors[0], 'filename' );
+ $this->dieRecoverableError( $permErrors, 'filename' );
}
}
try {
$result = $this->getContextResult();
} catch ( UploadStashException $e ) { // XXX: don't spam exception log
- list( $msg, $code ) = $this->handleStashException( get_class( $e ), $e->getMessage() );
- $this->dieUsage( $msg, $code );
+ $this->dieStatus( $this->handleStashException( $e ) );
}
$this->getResult()->addValue( null, $this->getModuleName(), $result );
// Check throttle after we've handled warnings
if ( UploadBase::isThrottled( $this->getUser() )
) {
- $this->dieUsageMsg( 'actionthrottledtext' );
+ $this->dieWithError( 'apierror-ratelimited' );
}
// This is the most common case -- a normal upload with no warnings
// Sanity check sizing
if ( $totalSoFar > $this->mParams['filesize'] ) {
- $this->dieUsage(
- 'Offset plus current chunk is greater than claimed file size', 'invalid-chunk'
- );
+ $this->dieWithError( 'apierror-invalid-chunk' );
}
// Enforce minimum chunk size
if ( $totalSoFar != $this->mParams['filesize'] && $chunkSize < $minChunkSize ) {
- $this->dieUsage(
- "Minimum chunk size is $minChunkSize bytes for non-final chunks", 'chunk-too-small'
- );
+ $this->dieWithError( [ 'apierror-chunk-too-small', Message::numParam( $minChunkSize ) ] );
}
if ( $this->mParams['offset'] == 0 ) {
$progress = UploadBase::getSessionStatus( $this->getUser(), $filekey );
if ( !$progress ) {
// Probably can't get here, but check anyway just in case
- $this->dieUsage( 'No chunked upload session with this key', 'stashfailed' );
+ $this->dieWithError( 'apierror-stashfailed-nosession', 'stashfailed' );
} elseif ( $progress['result'] !== 'Continue' || $progress['stage'] !== 'uploading' ) {
- $this->dieUsage(
- 'Chunked upload is already completed, check status for details', 'stashfailed'
- );
+ $this->dieWithError( 'apierror-stashfailed-complete', 'stashfailed' );
}
$status = $this->mUpload->addChunk(
if ( $status->isGood() && !$status->getValue() ) {
// Not actually a 'good' status...
- $status->fatal( new ApiRawMessage( 'Invalid stashed file', 'stashfailed' ) );
+ $status->fatal( new ApiMessage( 'apierror-stashinvalidfile', 'stashfailed' ) );
}
} catch ( Exception $e ) {
$debugMessage = 'Stashing temporary file failed: ' . get_class( $e ) . ' ' . $e->getMessage();
wfDebug( __METHOD__ . ' ' . $debugMessage . "\n" );
- $status = Status::newFatal( new ApiRawMessage( $e->getMessage(), 'stashfailed' ) );
+ $status = Status::newFatal( $this->getErrorFormatter()->getMessageFromException(
+ $e, [ 'wrap' => new ApiMessage( 'apierror-stashexception', 'stashfailed' ) ]
+ ) );
}
if ( $status->isGood() ) {
list( $exceptionType, $message ) = $status->getMessage()->getParams();
$debugMessage = 'Stashing temporary file failed: ' . $exceptionType . ' ' . $message;
wfDebug( __METHOD__ . ' ' . $debugMessage . "\n" );
- list( $msg, $code ) = $this->handleStashException( $exceptionType, $message );
- $status = Status::newFatal( new ApiRawMessage( $msg, $code ) );
}
// Bad status
if ( $failureMode !== 'optional' ) {
$this->dieStatus( $status );
} else {
- list( $code, $msg ) = $this->getErrorFromStatus( $status );
- $data['stashfailed'] = $msg;
+ $data['stasherrors'] = $this->getErrorFormatter()->arrayFromStatus( $status );
return null;
}
}
* Throw an error that the user can recover from by providing a better
* value for $parameter
*
- * @param array|string|MessageSpecifier $error Error suitable for passing to dieUsageMsg()
- * @param string $parameter Parameter that needs revising
- * @param array $data Optional extra data to pass to the user
- * @param string $code Error code to use if the error is unknown
- * @throws UsageException
+ * @param array $errors Array of Message objects, message keys, key+param
+ * arrays, or StatusValue::getErrors()-style arrays
+ * @param string|null $parameter Parameter that needs revising
+ * @throws ApiUsageException
*/
- private function dieRecoverableError( $error, $parameter, $data = [], $code = 'unknownerror' ) {
+ private function dieRecoverableError( $errors, $parameter = null ) {
$this->performStash( 'optional', $data );
- $data['invalidparameter'] = $parameter;
- $parsed = $this->parseMsg( $error );
- if ( isset( $parsed['data'] ) ) {
- $data = array_merge( $data, $parsed['data'] );
- }
- if ( $parsed['code'] === 'unknownerror' ) {
- $parsed['code'] = $code;
+ if ( $parameter ) {
+ $data['invalidparameter'] = $parameter;
}
- $this->dieUsage( $parsed['info'], $parsed['code'], 0, $data );
+ $sv = StatusValue::newGood();
+ foreach ( $errors as $error ) {
+ $msg = ApiMessage::create( $error );
+ $msg->setApiData( $msg->getApiData() + $data );
+ $sv->fatal( $msg );
+ }
+ $this->dieStatus( $sv );
}
/**
* @param Status $status
* @param string $overrideCode Error code to use if there isn't one from IApiMessage
* @param array|null $moreExtraData
- * @throws UsageException
+ * @throws ApiUsageException
*/
public function dieStatusWithCode( $status, $overrideCode, $moreExtraData = null ) {
- $extraData = null;
- list( $code, $msg ) = $this->getErrorFromStatus( $status, $extraData );
- $errors = $status->getErrorsByType( 'error' ) ?: $status->getErrorsByType( 'warning' );
- if ( !( $errors[0]['message'] instanceof IApiMessage ) ) {
- $code = $overrideCode;
- }
- if ( $moreExtraData ) {
- $extraData = $extraData ?: [];
- $extraData += $moreExtraData;
+ $sv = StatusValue::newGood();
+ foreach ( $status->getErrors() as $error ) {
+ $msg = ApiMessage::create( $error, $overrideCode );
+ if ( $moreExtraData ) {
+ $msg->setApiData( $msg->getApiData() + $moreExtraData );
+ }
+ $sv->fatal( $msg );
}
- $this->dieUsage( $msg, $code, 0, $extraData );
+ $this->dieStatus( $sv );
}
/**
if ( $this->mParams['filekey'] && $this->mParams['checkstatus'] ) {
$progress = UploadBase::getSessionStatus( $this->getUser(), $this->mParams['filekey'] );
if ( !$progress ) {
- $this->dieUsage( 'No result in status data', 'missingresult' );
+ $this->dieWithError( 'api-upload-missingresult', 'missingresult' );
} elseif ( !$progress['status']->isGood() ) {
$this->dieStatusWithCode( $progress['status'], 'stashfailed' );
}
// The following modules all require the filename parameter to be set
if ( is_null( $this->mParams['filename'] ) ) {
- $this->dieUsageMsg( [ 'missingparam', 'filename' ] );
+ $this->dieWithError( [ 'apierror-missingparam', 'filename' ] );
}
if ( $this->mParams['chunk'] ) {
$this->mUpload = new UploadFromChunks( $this->getUser() );
if ( isset( $this->mParams['filekey'] ) ) {
if ( $this->mParams['offset'] === 0 ) {
- $this->dieUsage( 'Cannot supply a filekey when offset is 0', 'badparams' );
+ $this->dieWithError( 'apierror-upload-filekeynotallowed', 'filekeynotallowed' );
}
// handle new chunk
);
} else {
if ( $this->mParams['offset'] !== 0 ) {
- $this->dieUsage( 'Must supply a filekey when offset is non-zero', 'badparams' );
+ $this->dieWithError( 'apierror-upload-filekeyneeded', 'filekeyneeded' );
}
// handle first chunk
} elseif ( isset( $this->mParams['filekey'] ) ) {
// Upload stashed in a previous request
if ( !UploadFromStash::isValidKey( $this->mParams['filekey'] ) ) {
- $this->dieUsageMsg( 'invalid-file-key' );
+ $this->dieWithError( 'apierror-invalid-file-key' );
}
$this->mUpload = new UploadFromStash( $this->getUser() );
} elseif ( isset( $this->mParams['url'] ) ) {
// Make sure upload by URL is enabled:
if ( !UploadFromUrl::isEnabled() ) {
- $this->dieUsageMsg( 'copyuploaddisabled' );
+ $this->dieWithError( 'copyuploaddisabled' );
}
if ( !UploadFromUrl::isAllowedHost( $this->mParams['url'] ) ) {
- $this->dieUsageMsg( 'copyuploadbaddomain' );
+ $this->dieWithError( 'apierror-copyuploadbaddomain' );
}
if ( !UploadFromUrl::isAllowedUrl( $this->mParams['url'] ) ) {
- $this->dieUsageMsg( 'copyuploadbadurl' );
+ $this->dieWithError( 'apierror-copyuploadbadurl' );
}
$this->mUpload = new UploadFromUrl;
if ( $permission !== true ) {
if ( !$user->isLoggedIn() ) {
- $this->dieUsageMsg( [ 'mustbeloggedin', 'upload' ] );
+ $this->dieWithError( [ 'apierror-mustbeloggedin', $this->msg( 'action-upload' ) ] );
}
- $this->dieUsageMsg( 'badaccess-groups' );
+ $this->dieStatus( User::newFatalPermissionDeniedStatus( $permission ) );
}
// Check blocks
* @param array $verification
*/
protected function checkVerification( array $verification ) {
- // @todo Move them to ApiBase's message map
switch ( $verification['status'] ) {
// Recoverable errors
case UploadBase::MIN_LENGTH_PARTNAME:
- $this->dieRecoverableError( 'filename-tooshort', 'filename' );
+ $this->dieRecoverableError( [ 'filename-tooshort' ], 'filename' );
break;
case UploadBase::ILLEGAL_FILENAME:
- $this->dieRecoverableError( 'illegal-filename', 'filename',
- [ 'filename' => $verification['filtered'] ] );
+ $this->dieRecoverableError(
+ [ ApiMessage::create(
+ 'illegal-filename', null, [ 'filename' => $verification['filtered'] ]
+ ) ], 'filename'
+ );
break;
case UploadBase::FILENAME_TOO_LONG:
- $this->dieRecoverableError( 'filename-toolong', 'filename' );
+ $this->dieRecoverableError( [ 'filename-toolong' ], 'filename' );
break;
case UploadBase::FILETYPE_MISSING:
- $this->dieRecoverableError( 'filetype-missing', 'filename' );
+ $this->dieRecoverableError( [ 'filetype-missing' ], 'filename' );
break;
case UploadBase::WINDOWS_NONASCII_FILENAME:
- $this->dieRecoverableError( 'windows-nonascii-filename', 'filename' );
+ $this->dieRecoverableError( [ 'windows-nonascii-filename' ], 'filename' );
break;
// Unrecoverable errors
case UploadBase::EMPTY_FILE:
- $this->dieUsage( 'The file you submitted was empty', 'empty-file' );
+ $this->dieWithError( 'empty-file' );
break;
case UploadBase::FILE_TOO_LARGE:
- $this->dieUsage( 'The file you submitted was too large', 'file-too-large' );
+ $this->dieWithError( 'file-too-large' );
break;
case UploadBase::FILETYPE_BADTYPE:
'filetype' => $verification['finalExt'],
'allowed' => array_values( array_unique( $this->getConfig()->get( 'FileExtensions' ) ) )
];
+ $extensions = array_unique( $this->getConfig()->get( 'FileExtensions' ) );
+ $msg = [
+ 'filetype-banned-type',
+ null, // filled in below
+ Message::listParam( $extensions, 'comma' ),
+ count( $extensions ),
+ null, // filled in below
+ ];
ApiResult::setIndexedTagName( $extradata['allowed'], 'ext' );
- $msg = 'Filetype not permitted: ';
if ( isset( $verification['blacklistedExt'] ) ) {
- $msg .= implode( ', ', $verification['blacklistedExt'] );
+ $msg[1] = Message::listParam( $verification['blacklistedExt'], 'comma' );
+ $msg[4] = count( $verification['blacklistedExt'] );
$extradata['blacklisted'] = array_values( $verification['blacklistedExt'] );
ApiResult::setIndexedTagName( $extradata['blacklisted'], 'ext' );
} else {
- $msg .= $verification['finalExt'];
+ $msg[1] = $verification['finalExt'];
+ $msg[4] = 1;
}
- $this->dieUsage( $msg, 'filetype-banned', 0, $extradata );
+
+ $this->dieWithError( $msg, 'filetype-banned', $extradata );
break;
+
case UploadBase::VERIFICATION_ERROR:
- $parsed = $this->parseMsg( $verification['details'] );
- $info = "This file did not pass file verification: {$parsed['info']}";
- if ( $verification['details'][0] instanceof IApiMessage ) {
- $code = $parsed['code'];
- } else {
- // For backwards-compatibility, all of the errors from UploadBase::verifyFile() are
- // reported as 'verification-error', and the real error code is reported in 'details'.
- $code = 'verification-error';
- }
- if ( $verification['details'][0] instanceof IApiMessage ) {
- $msg = $verification['details'][0];
+ $msg = ApiMessage::create( $verification['details'], 'verification-error' );
+ if ( $verification['details'][0] instanceof MessageSpecifier ) {
$details = array_merge( [ $msg->getKey() ], $msg->getParams() );
} else {
$details = $verification['details'];
}
ApiResult::setIndexedTagName( $details, 'detail' );
- $data = [ 'details' => $details ];
- if ( isset( $parsed['data'] ) ) {
- $data = array_merge( $data, $parsed['data'] );
- }
-
- $this->dieUsage( $info, $code, 0, $data );
+ $msg->setApiData( $msg->getApiData() + [ 'details' => $details ] );
+ $this->dieWithError( $msg );
break;
+
case UploadBase::HOOK_ABORTED:
- if ( is_array( $verification['error'] ) ) {
- $params = $verification['error'];
- } elseif ( $verification['error'] !== '' ) {
- $params = [ $verification['error'] ];
- } else {
- $params = [ 'hookaborted' ];
- }
- $key = array_shift( $params );
- $msg = $this->msg( $key, $params )->inLanguage( 'en' )->useDatabase( false )->text();
- $this->dieUsage( $msg, 'hookaborted', 0, [ 'details' => $verification['error'] ] );
+ $msg = $verification['error'] === '' ? 'hookaborted' : $verification['error'];
+ $this->dieWithError( $msg, 'hookaborted', [ 'details' => $verification['error'] ] );
break;
default:
- $this->dieUsage( 'An unknown error occurred', 'unknown-error',
- 0, [ 'details' => [ 'code' => $verification['status'] ] ] );
+ $this->dieWithError( 'apierror-unknownerror-nocode', 'unknown-error',
+ [ 'details' => [ 'code' => $verification['status'] ] ] );
break;
}
}
/**
* Handles a stash exception, giving a useful error to the user.
- * @param string $exceptionType Class name of the exception we encountered.
- * @param string $message Message of the exception we encountered.
- * @return array Array of message and code, suitable for passing to dieUsage()
+ * @todo Internationalize the exceptions then get rid of this
+ * @param Exception $e
+ * @return StatusValue
*/
- protected function handleStashException( $exceptionType, $message ) {
- switch ( $exceptionType ) {
+ protected function handleStashException( $e ) {
+ switch ( get_class( $e ) ) {
case 'UploadStashFileNotFoundException':
- return [
- 'Could not find the file in the stash: ' . $message,
- 'stashedfilenotfound'
- ];
+ $wrap = 'apierror-stashedfilenotfound';
+ break;
case 'UploadStashBadPathException':
- return [
- 'File key of improper format or otherwise invalid: ' . $message,
- 'stashpathinvalid'
- ];
+ $wrap = 'apierror-stashpathinvalid';
+ break;
case 'UploadStashFileException':
- return [
- 'Could not store upload in the stash: ' . $message,
- 'stashfilestorage'
- ];
+ $wrap = 'apierror-stashfilestorage';
+ break;
case 'UploadStashZeroLengthFileException':
- return [
- 'File is of zero length, and could not be stored in the stash: ' .
- $message,
- 'stashzerolength'
- ];
+ $wrap = 'apierror-stashzerolength';
+ break;
case 'UploadStashNotLoggedInException':
- return [ 'Not logged in: ' . $message, 'stashnotloggedin' ];
+ return StatusValue::newFatal( ApiMessage::create(
+ [ 'apierror-mustbeloggedin', $this->msg( 'action-upload' ) ], 'stashnotloggedin'
+ ) );
case 'UploadStashWrongOwnerException':
- return [ 'Wrong owner: ' . $message, 'stashwrongowner' ];
+ $wrap = 'apierror-stashwrongowner';
+ break;
case 'UploadStashNoSuchKeyException':
- return [ 'No such filekey: ' . $message, 'stashnosuchfilekey' ];
+ $wrap = 'apierror-stashnosuchfilekey';
+ break;
default:
- return [ $exceptionType . ': ' . $message, 'stasherror' ];
+ $wrap = [ 'uploadstash-exception', get_class( $e ) ];
+ break;
}
+ return StatusValue::newFatal(
+ $this->getErrorFormatter()->getMessageFromException( $e, [ 'wrap' => $wrap ] )
+ );
}
/**
if ( $this->mParams['async'] ) {
$progress = UploadBase::getSessionStatus( $this->getUser(), $this->mParams['filekey'] );
if ( $progress && $progress['result'] === 'Poll' ) {
- $this->dieUsage( 'Upload from stash already in progress.', 'publishfailed' );
+ $this->dieWithError( 'apierror-upload-inprogress', 'publishfailed' );
}
UploadBase::setSessionStatus(
$this->getUser(),
$this->mParams['text'], $watch, $this->getUser(), $this->mParams['tags'] );
if ( !$status->isGood() ) {
- // Is there really no better way to do this?
- $errors = $status->getErrorsByType( 'error' );
- $msg = array_merge( [ $errors[0]['message'] ], $errors[0]['params'] );
- $data = $status->getErrorsArray();
- ApiResult::setIndexedTagName( $data, 'error' );
- // For backwards-compatibility, we use the 'internal-error' fallback key and merge $data
- // into the root of the response (rather than something sane like [ 'details' => $data ]).
- $this->dieRecoverableError( $msg, null, $data, 'internal-error' );
+ $this->dieRecoverableError( $status->getErrors() );
}
$result['result'] = 'Success';
}