55 parent::__construct( $mainModule, $moduleName );
59 $this->watchlistExpiryEnabled = $this->
getConfig()->
get(
'WatchlistExpiry' );
60 $this->watchlistMaxDuration = $this->
getConfig()->get(
'WatchlistExpiryMaxDuration' );
67 if ( !UploadBase::isEnabled() ) {
75 $request = $this->
getMain()->getRequest();
77 $this->mParams[
'async'] = ( $this->mParams[
'async'] &&
78 $this->
getConfig()->get(
'EnableAsyncUploads' ) );
80 $this->mParams[
'file'] = $request->getFileName(
'file' );
81 $this->mParams[
'chunk'] = $request->getFileName(
'chunk' );
84 if ( !$this->mParams[
'filekey'] && $this->mParams[
'sessionkey'] ) {
85 $this->mParams[
'filekey'] = $this->mParams[
'sessionkey'];
92 } elseif ( !isset( $this->mUpload ) ) {
93 $this->
dieDebug( __METHOD__,
'No upload module set' );
104 $status = $this->mUpload->fetchFile();
105 if ( !$status->isGood() ) {
115 if ( !$this->mParams[
'stash'] ) {
116 $permErrors = $this->mUpload->verifyTitlePermissions( $user );
117 if ( $permErrors !==
true ) {
133 if ( $result[
'result'] ===
'Success' ) {
134 $imageinfo = $this->mUpload->getImageInfo( $this->
getResult() );
139 $this->mUpload->cleanupTempFile();
148 if ( $warnings && !$this->mParams[
'ignorewarnings'] ) {
151 } elseif ( $this->mParams[
'chunk'] ) {
154 } elseif ( $this->mParams[
'stash'] ) {
160 if ( UploadBase::isThrottled( $this->
getUser() )
177 $result[
'result'] =
'Success';
178 if ( $warnings && count( $warnings ) > 0 ) {
179 $result[
'warnings'] = $warnings;
195 $result[
'result'] =
'Warning';
196 $result[
'warnings'] = $warnings;
211 $configured = $config->
get(
'MinUploadChunkSize' );
216 ini_get(
'post_max_size' ),
225 UploadBase::getMaxUploadSize(
'file' ),
226 UploadBase::getMaxPhpUploadSize(),
239 if ( $warnings && count( $warnings ) > 0 ) {
240 $result[
'warnings'] = $warnings;
243 $request = $this->
getMain()->getRequest();
244 $chunkPath = $request->getFileTempname(
'chunk' );
245 $chunkSize = $request->getUpload(
'chunk' )->getSize();
246 $totalSoFar = $this->mParams[
'offset'] + $chunkSize;
250 if ( $totalSoFar > $this->mParams[
'filesize'] ) {
255 if ( $totalSoFar != $this->mParams[
'filesize'] && $chunkSize < $minChunkSize ) {
259 if ( $this->mParams[
'offset'] == 0 ) {
262 $filekey = $this->mParams[
'filekey'];
265 $progress = UploadBase::getSessionStatus( $this->
getUser(), $filekey );
268 $this->
dieWithError(
'apierror-stashfailed-nosession',
'stashfailed' );
269 } elseif ( $progress[
'result'] !==
'Continue' || $progress[
'stage'] !==
'uploading' ) {
270 $this->
dieWithError(
'apierror-stashfailed-complete',
'stashfailed' );
273 $status = $this->mUpload->addChunk(
274 $chunkPath, $chunkSize, $this->mParams[
'offset'] );
275 if ( !$status->isGood() ) {
277 'offset' => $this->mUpload->getOffset(),
285 if ( $totalSoFar == $this->mParams[
'filesize'] ) {
286 if ( $this->mParams[
'async'] ) {
287 UploadBase::setSessionStatus(
290 [
'result' =>
'Poll',
291 'stage' =>
'queued',
'status' => Status::newGood() ]
294 Title::makeTitle(
NS_FILE, $filekey ),
296 'filename' => $this->mParams[
'filename'],
297 'filekey' => $filekey,
301 $result[
'result'] =
'Poll';
302 $result[
'stage'] =
'queued';
304 $status = $this->mUpload->concatenateChunks();
305 if ( !$status->isGood() ) {
306 UploadBase::setSessionStatus(
309 [
'result' =>
'Failure',
'stage' =>
'assembling',
'status' => $status ]
317 $result[
'warnings'] = $warnings;
322 UploadBase::setSessionStatus( $this->
getUser(), $filekey,
false );
323 $this->mUpload->stash->removeFile( $filekey );
324 $filekey = $this->mUpload->getStashFile()->getFileKey();
326 $result[
'result'] =
'Success';
329 UploadBase::setSessionStatus(
333 'result' =>
'Continue',
334 'stage' =>
'uploading',
335 'offset' => $totalSoFar,
336 'status' => Status::newGood(),
339 $result[
'result'] =
'Continue';
340 $result[
'offset'] = $totalSoFar;
343 $result[
'filekey'] = $filekey;
361 $isPartial = (bool)$this->mParams[
'chunk'];
363 $status = $this->mUpload->tryStashFile( $this->
getUser(), $isPartial );
365 if ( $status->isGood() && !$status->getValue() ) {
367 $status->fatal(
new ApiMessage(
'apierror-stashinvalidfile',
'stashfailed' ) );
369 }
catch ( Exception $e ) {
370 $debugMessage =
'Stashing temporary file failed: ' . get_class( $e ) .
' ' . $e->getMessage();
371 wfDebug( __METHOD__ .
' ' . $debugMessage );
373 $e, [
'wrap' =>
new ApiMessage(
'apierror-stashexception',
'stashfailed' ) ]
377 if ( $status->isGood() ) {
378 $stashFile = $status->getValue();
379 $data[
'filekey'] = $stashFile->getFileKey();
381 $data[
'sessionkey'] = $data[
'filekey'];
382 return $data[
'filekey'];
385 if ( $status->getMessage()->getKey() ===
'uploadstash-exception' ) {
388 list( $exceptionType, $message ) = $status->getMessage()->getParams();
389 $debugMessage =
'Stashing temporary file failed: ' . $exceptionType .
' ' . $message;
390 wfDebug( __METHOD__ .
' ' . $debugMessage );
394 if ( $failureMode !==
'optional' ) {
415 $data[
'invalidparameter'] = $parameter;
418 $sv = StatusValue::newGood();
419 foreach ( $errors as $error ) {
420 $msg = ApiMessage::create( $error );
421 $msg->setApiData( $msg->getApiData() + $data );
437 $sv = StatusValue::newGood();
438 foreach ( $status->getErrors() as $error ) {
439 $msg = ApiMessage::create( $error, $overrideCode );
440 if ( $moreExtraData ) {
441 $msg->setApiData( $msg->getApiData() + $moreExtraData );
457 $request = $this->
getMain()->getRequest();
460 if ( !$this->mParams[
'chunk'] ) {
462 'filekey',
'file',
'url' );
466 if ( $this->mParams[
'filekey'] && $this->mParams[
'checkstatus'] ) {
467 $progress = UploadBase::getSessionStatus( $this->
getUser(), $this->mParams[
'filekey'] );
469 $this->
dieWithError(
'apierror-upload-missingresult',
'missingresult' );
470 } elseif ( !$progress[
'status']->isGood() ) {
473 if ( isset( $progress[
'status']->value[
'verification'] ) ) {
476 if ( isset( $progress[
'status']->value[
'warnings'] ) ) {
479 $progress[
'warnings'] = $warnings;
482 unset( $progress[
'status'] );
484 if ( isset( $progress[
'imageinfo'] ) ) {
485 $imageinfo = $progress[
'imageinfo'];
486 unset( $progress[
'imageinfo'] );
500 if ( $this->mParams[
'filename'] ===
null ) {
501 $this->
dieWithError( [
'apierror-missingparam',
'filename' ] );
504 if ( $this->mParams[
'chunk'] ) {
507 if ( isset( $this->mParams[
'filekey'] ) ) {
508 if ( $this->mParams[
'offset'] === 0 ) {
509 $this->
dieWithError(
'apierror-upload-filekeynotallowed',
'filekeynotallowed' );
513 $this->mUpload->continueChunks(
514 $this->mParams[
'filename'],
515 $this->mParams[
'filekey'],
516 $request->getUpload(
'chunk' )
519 if ( $this->mParams[
'offset'] !== 0 ) {
520 $this->
dieWithError(
'apierror-upload-filekeyneeded',
'filekeyneeded' );
524 $this->mUpload->initialize(
525 $this->mParams[
'filename'],
526 $request->getUpload(
'chunk' )
529 } elseif ( isset( $this->mParams[
'filekey'] ) ) {
538 $this->mUpload->initialize(
539 $this->mParams[
'filekey'], $this->mParams[
'filename'], !$this->mParams[
'async']
541 } elseif ( isset( $this->mParams[
'file'] ) ) {
545 if ( $this->mParams[
'async'] ) {
546 $this->
dieWithError(
'apierror-cannot-async-upload-file' );
550 $this->mUpload->initialize(
551 $this->mParams[
'filename'],
552 $request->getUpload(
'file' )
554 } elseif ( isset( $this->mParams[
'url'] ) ) {
569 $this->mUpload->
initialize( $this->mParams[
'filename'],
570 $this->mParams[
'url'] );
583 $permission = $this->mUpload->isAllowed( $user );
585 if ( $permission !==
true ) {
586 if ( !$user->isRegistered() ) {
587 $this->
dieWithError( [
'apierror-mustbeloggedin', $this->
msg(
'action-upload' ) ] );
594 if ( $user->isBlockedFromUpload() ) {
599 if ( $user->isBlockedGlobally() ) {
608 if ( $this->mParams[
'chunk'] ) {
609 $maxSize = UploadBase::getMaxUploadSize();
610 if ( $this->mParams[
'filesize'] > $maxSize ) {
613 if ( !$this->mUpload->getTitle() ) {
618 $verification = $this->mUpload->validateName();
619 if ( $verification ===
true ) {
622 } elseif ( $this->mParams[
'async'] && $this->mParams[
'filekey'] ) {
626 $verification = $this->mUpload->validateName();
627 if ( $verification ===
true ) {
631 wfDebug( __METHOD__ .
" about to verify" );
633 $verification = $this->mUpload->verifyUpload();
634 if ( $verification[
'status'] === UploadBase::OK ) {
647 switch ( $verification[
'status'] ) {
649 case UploadBase::MIN_LENGTH_PARTNAME:
652 case UploadBase::ILLEGAL_FILENAME:
654 [ ApiMessage::create(
655 'illegal-filename',
null, [
'filename' => $verification[
'filtered'] ]
659 case UploadBase::FILENAME_TOO_LONG:
662 case UploadBase::FILETYPE_MISSING:
665 case UploadBase::WINDOWS_NONASCII_FILENAME:
669 case UploadBase::EMPTY_FILE:
672 case UploadBase::FILE_TOO_LARGE:
676 case UploadBase::FILETYPE_BADTYPE:
678 'filetype' => $verification[
'finalExt'],
679 'allowed' => array_values( array_unique( $this->
getConfig()->
get(
'FileExtensions' ) ) )
681 $extensions = array_unique( $this->
getConfig()->
get(
'FileExtensions' ) );
683 'filetype-banned-type',
686 count( $extensions ),
689 ApiResult::setIndexedTagName( $extradata[
'allowed'],
'ext' );
691 if ( isset( $verification[
'blacklistedExt'] ) ) {
693 $msg[4] = count( $verification[
'blacklistedExt'] );
694 $extradata[
'blacklisted'] = array_values( $verification[
'blacklistedExt'] );
695 ApiResult::setIndexedTagName( $extradata[
'blacklisted'],
'ext' );
697 $msg[1] = $verification[
'finalExt'];
701 $this->
dieWithError( $msg,
'filetype-banned', $extradata );
704 case UploadBase::VERIFICATION_ERROR:
705 $msg = ApiMessage::create( $verification[
'details'],
'verification-error' );
707 $details = array_merge( [ $msg->getKey() ], $msg->getParams() );
709 $details = $verification[
'details'];
711 ApiResult::setIndexedTagName( $details,
'detail' );
712 $msg->setApiData( $msg->getApiData() + [
'details' => $details ] );
717 case UploadBase::HOOK_ABORTED:
718 $msg = $verification[
'error'] ===
'' ?
'hookaborted' : $verification[
'error'];
719 $this->
dieWithError( $msg,
'hookaborted', [
'details' => $verification[
'error'] ] );
722 $this->
dieWithError(
'apierror-unknownerror-nocode',
'unknown-error',
723 [
'details' => [
'code' => $verification[
'status'] ] ] );
735 $warnings = UploadBase::makeWarningsSerializable(
736 $this->mUpload->checkWarnings( $this->getUser() )
745 ApiResult::setIndexedTagName( $warnings,
'warning' );
747 if ( isset( $warnings[
'duplicate'] ) ) {
748 $dupes = array_column( $warnings[
'duplicate'],
'fileName' );
749 ApiResult::setIndexedTagName( $dupes,
'duplicate' );
750 $warnings[
'duplicate'] = $dupes;
753 if ( isset( $warnings[
'exists'] ) ) {
754 $warning = $warnings[
'exists'];
755 unset( $warnings[
'exists'] );
756 $localFile = $warning[
'normalizedFile'] ?? $warning[
'file'];
757 $warnings[$warning[
'warning']] = $localFile[
'fileName'];
760 if ( isset( $warnings[
'no-change'] ) ) {
761 $file = $warnings[
'no-change'];
762 unset( $warnings[
'no-change'] );
764 $warnings[
'nochange'] = [
769 if ( isset( $warnings[
'duplicate-version'] ) ) {
771 foreach ( $warnings[
'duplicate-version'] as $dupe ) {
773 'timestamp' =>
wfTimestamp( TS_ISO_8601, $dupe[
'timestamp'] )
776 unset( $warnings[
'duplicate-version'] );
778 ApiResult::setIndexedTagName( $dupes,
'ver' );
779 $warnings[
'duplicateversions'] = $dupes;
793 switch ( get_class( $e ) ) {
794 case UploadStashFileNotFoundException::class:
795 $wrap =
'apierror-stashedfilenotfound';
797 case UploadStashBadPathException::class:
798 $wrap =
'apierror-stashpathinvalid';
800 case UploadStashFileException::class:
801 $wrap =
'apierror-stashfilestorage';
803 case UploadStashZeroLengthFileException::class:
804 $wrap =
'apierror-stashzerolength';
806 case UploadStashNotLoggedInException::class:
807 return StatusValue::newFatal( ApiMessage::create(
808 [
'apierror-mustbeloggedin', $this->
msg(
'action-upload' ) ],
'stashnotloggedin'
810 case UploadStashWrongOwnerException::class:
811 $wrap =
'apierror-stashwrongowner';
813 case UploadStashNoSuchKeyException::class:
814 $wrap =
'apierror-stashnosuchfilekey';
817 $wrap = [
'uploadstash-exception', get_class( $e ) ];
820 return StatusValue::newFatal(
834 if ( $this->mParams[
'text'] ===
null ) {
835 $this->mParams[
'text'] = $this->mParams[
'comment'];
839 $file = $this->mUpload->getLocalFile();
849 $this->mParams[
'watchlist'],
$title, $user,
'watchdefault'
852 if ( !$watch && $this->mParams[
'watchlist'] ==
'preferences' && !
$file->exists() ) {
861 if ( $this->mParams[
'watch'] ) {
865 if ( $this->mParams[
'tags'] ) {
867 if ( !$status->isOK() ) {
874 if ( $this->mParams[
'async'] ) {
875 $progress = UploadBase::getSessionStatus( $this->
getUser(), $this->mParams[
'filekey'] );
876 if ( $progress && $progress[
'result'] ===
'Poll' ) {
877 $this->
dieWithError(
'apierror-upload-inprogress',
'publishfailed' );
879 UploadBase::setSessionStatus(
881 $this->mParams[
'filekey'],
882 [
'result' =>
'Poll',
'stage' =>
'queued',
'status' => Status::newGood() ]
885 Title::makeTitle(
NS_FILE, $this->mParams[
'filename'] ),
887 'filename' => $this->mParams[
'filename'],
888 'filekey' => $this->mParams[
'filekey'],
889 'comment' => $this->mParams[
'comment'],
890 'tags' => $this->mParams[
'tags'],
891 'text' => $this->mParams[
'text'],
893 'watchlistexpiry' => $watchlistExpiry,
897 $result[
'result'] =
'Poll';
898 $result[
'stage'] =
'queued';
901 $status = $this->mUpload->performUpload(
902 $this->mParams[
'comment'],
903 $this->mParams[
'text'],
906 $this->mParams[
'tags'],
910 if ( !$status->isGood() ) {
913 $result[
'result'] =
'Success';
916 $result[
'filename'] =
$file->getName();
917 if ( $warnings && count( $warnings ) > 0 ) {
918 $result[
'warnings'] = $warnings;
962 'ignorewarnings' =>
false,
987 'checkstatus' =>
false,
999 'action=upload&filename=Wiki.png' .
1000 '&url=http%3A//upload.wikimedia.org/wikipedia/en/b/bc/Wiki.png&token=123ABC'
1001 =>
'apihelp-upload-example-url',
1002 'action=upload&filename=Wiki.png&filekey=filekey&ignorewarnings=1&token=123ABC'
1003 =>
'apihelp-upload-example-filekey',
1008 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.
WatchlistManager $watchlistManager
UserOptionsLookup $userOptionsLookup
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.
requireOnlyOneParameter( $params,... $required)
Die if none or more than one of a certain set of parameters is set and not false.
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.
dieBlocked(Block $block)
Throw an ApiUsageException, which will (if uncaught) call the main module's error handler and die wit...
This is the main API class, used for both external and internal processing.
Extension of Message implementing IApiMessage @newable.
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)
__construct(ApiMain $mainModule, $moduleName, JobQueueGroup $jobQueueGroup, WatchlistManager $watchlistManager, UserOptionsLookup $userOptionsLookup)
getWarningsResult( $warnings)
Get Warnings Result.
JobQueueGroup $jobQueueGroup
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.
getApiWarnings()
Check warnings.
Assemble the segments of a chunked upload.
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.
Class to handle enqueueing of background jobs.
get( $type)
Get the job queue object for a given queue type.
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 allowlist 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.