36 parent::__construct( $mainModule, $moduleName, $modulePrefix );
38 $this->watchlistExpiryEnabled = $this->
getConfig()->get(
'WatchlistExpiry' );
39 $this->watchlistMaxDuration = $this->
getConfig()->get(
'WatchlistExpiryMaxDuration' );
44 if ( !UploadBase::isEnabled() ) {
52 $request = $this->
getMain()->getRequest();
54 $this->mParams[
'async'] = ( $this->mParams[
'async'] &&
55 $this->
getConfig()->get(
'EnableAsyncUploads' ) );
57 $this->mParams[
'file'] = $request->getFileName(
'file' );
58 $this->mParams[
'chunk'] = $request->getFileName(
'chunk' );
61 if ( !$this->mParams[
'filekey'] && $this->mParams[
'sessionkey'] ) {
62 $this->mParams[
'filekey'] = $this->mParams[
'sessionkey'];
69 } elseif ( !isset( $this->mUpload ) ) {
70 $this->
dieDebug( __METHOD__,
'No upload module set' );
81 $status = $this->mUpload->fetchFile();
82 if ( !$status->isGood() ) {
92 if ( !$this->mParams[
'stash'] ) {
93 $permErrors = $this->mUpload->verifyTitlePermissions( $user );
94 if ( $permErrors !==
true ) {
110 if ( $result[
'result'] ===
'Success' ) {
111 $imageinfo = $this->mUpload->getImageInfo( $this->
getResult() );
116 $this->mUpload->cleanupTempFile();
125 if ( $warnings && !$this->mParams[
'ignorewarnings'] ) {
128 } elseif ( $this->mParams[
'chunk'] ) {
131 } elseif ( $this->mParams[
'stash'] ) {
137 if ( UploadBase::isThrottled( $this->
getUser() )
154 $result[
'result'] =
'Success';
155 if ( $warnings && count( $warnings ) > 0 ) {
156 $result[
'warnings'] = $warnings;
172 $result[
'result'] =
'Warning';
173 $result[
'warnings'] = $warnings;
188 $configured = $config->
get(
'MinUploadChunkSize' );
193 ini_get(
'post_max_size' ),
202 UploadBase::getMaxUploadSize(
'file' ),
203 UploadBase::getMaxPhpUploadSize(),
216 if ( $warnings && count( $warnings ) > 0 ) {
217 $result[
'warnings'] = $warnings;
220 $request = $this->
getMain()->getRequest();
221 $chunkPath = $request->getFileTempname(
'chunk' );
222 $chunkSize = $request->getUpload(
'chunk' )->getSize();
223 $totalSoFar = $this->mParams[
'offset'] + $chunkSize;
227 if ( $totalSoFar > $this->mParams[
'filesize'] ) {
232 if ( $totalSoFar != $this->mParams[
'filesize'] && $chunkSize < $minChunkSize ) {
236 if ( $this->mParams[
'offset'] == 0 ) {
239 $filekey = $this->mParams[
'filekey'];
242 $progress = UploadBase::getSessionStatus( $this->
getUser(), $filekey );
245 $this->
dieWithError(
'apierror-stashfailed-nosession',
'stashfailed' );
246 } elseif ( $progress[
'result'] !==
'Continue' || $progress[
'stage'] !==
'uploading' ) {
247 $this->
dieWithError(
'apierror-stashfailed-complete',
'stashfailed' );
250 $status = $this->mUpload->addChunk(
251 $chunkPath, $chunkSize, $this->mParams[
'offset'] );
252 if ( !$status->isGood() ) {
254 'offset' => $this->mUpload->getOffset(),
262 if ( $totalSoFar == $this->mParams[
'filesize'] ) {
263 if ( $this->mParams[
'async'] ) {
264 UploadBase::setSessionStatus(
267 [
'result' =>
'Poll',
268 'stage' =>
'queued',
'status' => Status::newGood() ]
271 Title::makeTitle(
NS_FILE, $filekey ),
273 'filename' => $this->mParams[
'filename'],
274 'filekey' => $filekey,
278 $result[
'result'] =
'Poll';
279 $result[
'stage'] =
'queued';
281 $status = $this->mUpload->concatenateChunks();
282 if ( !$status->isGood() ) {
283 UploadBase::setSessionStatus(
286 [
'result' =>
'Failure',
'stage' =>
'assembling',
'status' => $status ]
294 $result[
'warnings'] = $warnings;
299 UploadBase::setSessionStatus( $this->
getUser(), $filekey,
false );
300 $this->mUpload->stash->removeFile( $filekey );
301 $filekey = $this->mUpload->getStashFile()->getFileKey();
303 $result[
'result'] =
'Success';
306 UploadBase::setSessionStatus(
310 'result' =>
'Continue',
311 'stage' =>
'uploading',
312 'offset' => $totalSoFar,
313 'status' => Status::newGood(),
316 $result[
'result'] =
'Continue';
317 $result[
'offset'] = $totalSoFar;
320 $result[
'filekey'] = $filekey;
338 $isPartial = (bool)$this->mParams[
'chunk'];
340 $status = $this->mUpload->tryStashFile( $this->
getUser(), $isPartial );
342 if ( $status->isGood() && !$status->getValue() ) {
344 $status->fatal(
new ApiMessage(
'apierror-stashinvalidfile',
'stashfailed' ) );
346 }
catch ( Exception $e ) {
347 $debugMessage =
'Stashing temporary file failed: ' . get_class( $e ) .
' ' . $e->getMessage();
348 wfDebug( __METHOD__ .
' ' . $debugMessage );
350 $e, [
'wrap' =>
new ApiMessage(
'apierror-stashexception',
'stashfailed' ) ]
354 if ( $status->isGood() ) {
355 $stashFile = $status->getValue();
356 $data[
'filekey'] = $stashFile->getFileKey();
358 $data[
'sessionkey'] = $data[
'filekey'];
359 return $data[
'filekey'];
362 if ( $status->getMessage()->getKey() ===
'uploadstash-exception' ) {
365 list( $exceptionType, $message ) = $status->getMessage()->getParams();
366 $debugMessage =
'Stashing temporary file failed: ' . $exceptionType .
' ' . $message;
367 wfDebug( __METHOD__ .
' ' . $debugMessage );
371 if ( $failureMode !==
'optional' ) {
392 $data[
'invalidparameter'] = $parameter;
395 $sv = StatusValue::newGood();
396 foreach ( $errors as $error ) {
397 $msg = ApiMessage::create( $error );
398 $msg->setApiData( $msg->getApiData() + $data );
414 $sv = StatusValue::newGood();
415 foreach ( $status->getErrors() as $error ) {
416 $msg = ApiMessage::create( $error, $overrideCode );
417 if ( $moreExtraData ) {
418 $msg->setApiData( $msg->getApiData() + $moreExtraData );
434 $request = $this->
getMain()->getRequest();
437 if ( !$this->mParams[
'chunk'] ) {
439 'filekey',
'file',
'url' );
443 if ( $this->mParams[
'filekey'] && $this->mParams[
'checkstatus'] ) {
444 $progress = UploadBase::getSessionStatus( $this->
getUser(), $this->mParams[
'filekey'] );
446 $this->
dieWithError(
'apierror-upload-missingresult',
'missingresult' );
447 } elseif ( !$progress[
'status']->isGood() ) {
450 if ( isset( $progress[
'status']->value[
'verification'] ) ) {
453 if ( isset( $progress[
'status']->value[
'warnings'] ) ) {
456 $progress[
'warnings'] = $warnings;
459 unset( $progress[
'status'] );
461 if ( isset( $progress[
'imageinfo'] ) ) {
462 $imageinfo = $progress[
'imageinfo'];
463 unset( $progress[
'imageinfo'] );
477 if ( $this->mParams[
'filename'] ===
null ) {
478 $this->
dieWithError( [
'apierror-missingparam',
'filename' ] );
481 if ( $this->mParams[
'chunk'] ) {
484 if ( isset( $this->mParams[
'filekey'] ) ) {
485 if ( $this->mParams[
'offset'] === 0 ) {
486 $this->
dieWithError(
'apierror-upload-filekeynotallowed',
'filekeynotallowed' );
490 $this->mUpload->continueChunks(
491 $this->mParams[
'filename'],
492 $this->mParams[
'filekey'],
493 $request->getUpload(
'chunk' )
496 if ( $this->mParams[
'offset'] !== 0 ) {
497 $this->
dieWithError(
'apierror-upload-filekeyneeded',
'filekeyneeded' );
501 $this->mUpload->initialize(
502 $this->mParams[
'filename'],
503 $request->getUpload(
'chunk' )
506 } elseif ( isset( $this->mParams[
'filekey'] ) ) {
515 $this->mUpload->initialize(
516 $this->mParams[
'filekey'], $this->mParams[
'filename'], !$this->mParams[
'async']
518 } elseif ( isset( $this->mParams[
'file'] ) ) {
522 if ( $this->mParams[
'async'] ) {
523 $this->
dieWithError(
'apierror-cannot-async-upload-file' );
527 $this->mUpload->initialize(
528 $this->mParams[
'filename'],
529 $request->getUpload(
'file' )
531 } elseif ( isset( $this->mParams[
'url'] ) ) {
546 $this->mUpload->
initialize( $this->mParams[
'filename'],
547 $this->mParams[
'url'] );
560 $permission = $this->mUpload->isAllowed( $user );
562 if ( $permission !==
true ) {
563 if ( !$user->isLoggedIn() ) {
564 $this->
dieWithError( [
'apierror-mustbeloggedin', $this->
msg(
'action-upload' ) ] );
571 if ( $user->isBlockedFromUpload() ) {
576 if ( $user->isBlockedGlobally() ) {
585 if ( $this->mParams[
'chunk'] ) {
586 $maxSize = UploadBase::getMaxUploadSize();
587 if ( $this->mParams[
'filesize'] > $maxSize ) {
590 if ( !$this->mUpload->getTitle() ) {
595 $verification = $this->mUpload->validateName();
596 if ( $verification ===
true ) {
599 } elseif ( $this->mParams[
'async'] && $this->mParams[
'filekey'] ) {
603 $verification = $this->mUpload->validateName();
604 if ( $verification ===
true ) {
608 wfDebug( __METHOD__ .
" about to verify" );
610 $verification = $this->mUpload->verifyUpload();
611 if ( $verification[
'status'] === UploadBase::OK ) {
624 switch ( $verification[
'status'] ) {
626 case UploadBase::MIN_LENGTH_PARTNAME:
629 case UploadBase::ILLEGAL_FILENAME:
631 [ ApiMessage::create(
632 'illegal-filename',
null, [
'filename' => $verification[
'filtered'] ]
636 case UploadBase::FILENAME_TOO_LONG:
639 case UploadBase::FILETYPE_MISSING:
642 case UploadBase::WINDOWS_NONASCII_FILENAME:
647 case UploadBase::EMPTY_FILE:
650 case UploadBase::FILE_TOO_LARGE:
654 case UploadBase::FILETYPE_BADTYPE:
656 'filetype' => $verification[
'finalExt'],
657 'allowed' => array_values( array_unique( $this->
getConfig()->
get(
'FileExtensions' ) ) )
659 $extensions = array_unique( $this->
getConfig()->
get(
'FileExtensions' ) );
661 'filetype-banned-type',
664 count( $extensions ),
667 ApiResult::setIndexedTagName( $extradata[
'allowed'],
'ext' );
669 if ( isset( $verification[
'blacklistedExt'] ) ) {
671 $msg[4] = count( $verification[
'blacklistedExt'] );
672 $extradata[
'blacklisted'] = array_values( $verification[
'blacklistedExt'] );
673 ApiResult::setIndexedTagName( $extradata[
'blacklisted'],
'ext' );
675 $msg[1] = $verification[
'finalExt'];
679 $this->
dieWithError( $msg,
'filetype-banned', $extradata );
682 case UploadBase::VERIFICATION_ERROR:
683 $msg = ApiMessage::create( $verification[
'details'],
'verification-error' );
685 $details = array_merge( [ $msg->getKey() ], $msg->getParams() );
687 $details = $verification[
'details'];
689 ApiResult::setIndexedTagName( $details,
'detail' );
690 $msg->setApiData( $msg->getApiData() + [
'details' => $details ] );
695 case UploadBase::HOOK_ABORTED:
696 $msg = $verification[
'error'] ===
'' ?
'hookaborted' : $verification[
'error'];
697 $this->
dieWithError( $msg,
'hookaborted', [
'details' => $verification[
'error'] ] );
700 $this->
dieWithError(
'apierror-unknownerror-nocode',
'unknown-error',
701 [
'details' => [
'code' => $verification[
'status'] ] ] );
714 $warnings = UploadBase::makeWarningsSerializable(
715 $this->mUpload->checkWarnings( $this->getUser() )
724 ApiResult::setIndexedTagName( $warnings,
'warning' );
726 if ( isset( $warnings[
'duplicate'] ) ) {
728 foreach ( $warnings[
'duplicate'] as $dupe ) {
729 $dupes[] = $dupe[
'fileName'];
731 ApiResult::setIndexedTagName( $dupes,
'duplicate' );
732 $warnings[
'duplicate'] = $dupes;
735 if ( isset( $warnings[
'exists'] ) ) {
736 $warning = $warnings[
'exists'];
737 unset( $warnings[
'exists'] );
738 $localFile = $warning[
'normalizedFile'] ?? $warning[
'file'];
739 $warnings[$warning[
'warning']] = $localFile[
'fileName'];
742 if ( isset( $warnings[
'no-change'] ) ) {
743 $file = $warnings[
'no-change'];
744 unset( $warnings[
'no-change'] );
746 $warnings[
'nochange'] = [
751 if ( isset( $warnings[
'duplicate-version'] ) ) {
753 foreach ( $warnings[
'duplicate-version'] as $dupe ) {
755 'timestamp' =>
wfTimestamp( TS_ISO_8601, $dupe[
'timestamp'] )
758 unset( $warnings[
'duplicate-version'] );
760 ApiResult::setIndexedTagName( $dupes,
'ver' );
761 $warnings[
'duplicateversions'] = $dupes;
775 switch ( get_class( $e ) ) {
776 case UploadStashFileNotFoundException::class:
777 $wrap =
'apierror-stashedfilenotfound';
779 case UploadStashBadPathException::class:
780 $wrap =
'apierror-stashpathinvalid';
782 case UploadStashFileException::class:
783 $wrap =
'apierror-stashfilestorage';
785 case UploadStashZeroLengthFileException::class:
786 $wrap =
'apierror-stashzerolength';
788 case UploadStashNotLoggedInException::class:
789 return StatusValue::newFatal( ApiMessage::create(
790 [
'apierror-mustbeloggedin', $this->
msg(
'action-upload' ) ],
'stashnotloggedin'
792 case UploadStashWrongOwnerException::class:
793 $wrap =
'apierror-stashwrongowner';
795 case UploadStashNoSuchKeyException::class:
796 $wrap =
'apierror-stashnosuchfilekey';
799 $wrap = [
'uploadstash-exception', get_class( $e ) ];
802 return StatusValue::newFatal(
816 if ( $this->mParams[
'text'] ===
null ) {
817 $this->mParams[
'text'] = $this->mParams[
'comment'];
821 $file = $this->mUpload->getLocalFile();
831 $this->mParams[
'watchlist'],
$title, $user,
'watchdefault'
834 if ( !$watch && $this->mParams[
'watchlist'] ==
'preferences' && !
$file->exists() ) {
843 if ( $this->mParams[
'watch'] ) {
847 if ( $this->mParams[
'tags'] ) {
849 if ( !$status->isOK() ) {
856 if ( $this->mParams[
'async'] ) {
857 $progress = UploadBase::getSessionStatus( $this->
getUser(), $this->mParams[
'filekey'] );
858 if ( $progress && $progress[
'result'] ===
'Poll' ) {
859 $this->
dieWithError(
'apierror-upload-inprogress',
'publishfailed' );
861 UploadBase::setSessionStatus(
863 $this->mParams[
'filekey'],
864 [
'result' =>
'Poll',
'stage' =>
'queued',
'status' => Status::newGood() ]
867 Title::makeTitle(
NS_FILE, $this->mParams[
'filename'] ),
869 'filename' => $this->mParams[
'filename'],
870 'filekey' => $this->mParams[
'filekey'],
871 'comment' => $this->mParams[
'comment'],
872 'tags' => $this->mParams[
'tags'],
873 'text' => $this->mParams[
'text'],
875 'watchlistexpiry' => $watchlistExpiry,
879 $result[
'result'] =
'Poll';
880 $result[
'stage'] =
'queued';
883 $status = $this->mUpload->performUpload(
884 $this->mParams[
'comment'],
885 $this->mParams[
'text'],
888 $this->mParams[
'tags'],
892 if ( !$status->isGood() ) {
895 $result[
'result'] =
'Success';
898 $result[
'filename'] =
$file->getName();
899 if ( $warnings && count( $warnings ) > 0 ) {
900 $result[
'warnings'] = $warnings;
944 'ignorewarnings' =>
false,
969 'checkstatus' =>
false,
981 'action=upload&filename=Wiki.png' .
982 '&url=http%3A//upload.wikimedia.org/wikipedia/en/b/bc/Wiki.png&token=123ABC'
983 =>
'apihelp-upload-example-url',
984 'action=upload&filename=Wiki.png&filekey=filekey&ignorewarnings=1&token=123ABC'
985 =>
'apihelp-upload-example-filekey',
990 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Upload';
getWatchlistValue(string $watchlist, Title $title, User $user, ?string $userOption=null)
Return true if we're to watch the page, false if not.
getExpiryFromParams(array $params)
Get formatted expiry from the given parameters, or null if no expiry was provided.
getWatchlistParams(array $watchOptions=[])
Get additional allow params specific to watchlisting.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfShorthandToInteger(?string $string='', int $default=-1)
Converts shorthand byte notation to integer form.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
This abstract class implements many basic API functions, and is the base of all API classes.
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
static dieDebug( $method, $message)
Internal code errors should be reported with this method.
getMain()
Get the main module.
getErrorFormatter()
Get the error formatter Stable to override.
requireOnlyOneParameter( $params,... $required)
Die if none or more than one of a certain set of parameters is set and not false.
dieBlocked(AbstractBlock $block)
Throw an ApiUsageException, which will (if uncaught) call the main module's error handler and die wit...
getResult()
Get the result object.
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
getModuleName()
Get the name of the module being executed by this instance.
dieStatus(StatusValue $status)
Throw an ApiUsageException based on the Status object.
This is the main API class, used for both external and internal processing.
Extension of Message implementing IApiMessage @newable.
__construct(ApiMain $mainModule, $moduleName, $modulePrefix='')
Stable to call.
performStash( $failureMode, &$data=null)
Stash the file and add the file key, or error information if it fails, to the data.
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
checkPermissions( $user)
Checks that the user has permissions to perform this upload.
dieRecoverableError( $errors, $parameter=null)
Throw an error that the user can recover from by providing a better value for $parameter.
verifyUpload()
Performs file verification, dies on error.
UploadBase UploadFromChunks $mUpload
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
transformWarnings( $warnings)
handleStashException( $e)
Handles a stash exception, giving a useful error to the user.
getHelpUrls()
Return links to more detailed help pages about the module.
dieStatusWithCode( $status, $overrideCode, $moreExtraData=null)
Like dieStatus(), but always uses $overrideCode for the error code, unless the code comes from IApiMe...
isWriteMode()
Indicates whether this module requires write mode.
static getMinUploadChunkSize(Config $config)
getWarningsResult( $warnings)
Get Warnings Result.
getContextResult()
Get an upload result based on upload context.
getChunkResult( $warnings)
Get the result of a chunk upload.
getExamplesMessages()
Returns usage examples for this module.
selectUploadModule()
Select an upload module and set it to mUpload.
getStashResult( $warnings)
Get Stash Result, throws an exception if the file could not be stashed.
needsToken()
Returns the token type this module requires in order to execute.
performUpload( $warnings)
Perform the actual upload.
checkVerification(array $verification)
Performs file verification, dies on error.
mustBePosted()
Indicates whether this module must be called with a POST request Stable to override.
getApiWarnings()
Check warnings.
Assemble the segments of a chunked upload.
getUser()
Stable to override.
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
exportSession()
Export the resolved user IP, HTTP headers, user ID, and session ID.
getContext()
Get the base IContextSource object.
static listParam(array $list, $type='text')
Upload a file from the upload stash into the local file repo.
UploadBase and subclasses are the backend of MediaWiki's file uploads.
Implements uploading from chunks.
Implements regular file uploads.
Implements uploading from previously stored file.
Implements uploading from a HTTP resource.
initialize( $name, $url)
Entry point for API upload.
static isAllowedHost( $url)
Checks whether the URL is for an allowed host The domains in the whitelist can include wildcard chara...
static isAllowedUrl( $url)
Checks whether the URL is not allowed.
static isEnabled()
Checks if the upload from URL feature is enabled.
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
trait ApiWatchlistTrait
An ApiWatchlistTrait adds class properties and convenience methods for APIs that allow you to watch a...
Interface for configuration instances.
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.