61 parent::__construct( $mainModule, $moduleName );
62 $this->jobQueueGroup = $jobQueueGroup;
65 $this->watchlistExpiryEnabled = $this->
getConfig()->get( MainConfigNames::WatchlistExpiry );
66 $this->watchlistMaxDuration =
67 $this->
getConfig()->get( MainConfigNames::WatchlistExpiryMaxDuration );
68 $this->watchlistManager = $watchlistManager;
69 $this->userOptionsLookup = $userOptionsLookup;
82 $request = $this->
getMain()->getRequest();
84 $this->mParams[
'async'] = ( $this->mParams[
'async'] &&
85 $this->
getConfig()->get( MainConfigNames::EnableAsyncUploads ) );
87 $this->mParams[
'file'] = $request->getFileName(
'file' );
88 $this->mParams[
'chunk'] = $request->getFileName(
'chunk' );
91 if ( !$this->mParams[
'filekey'] && $this->mParams[
'sessionkey'] ) {
92 $this->mParams[
'filekey'] = $this->mParams[
'sessionkey'];
99 } elseif ( !isset( $this->mUpload ) ) {
100 $this->
dieDebug( __METHOD__,
'No upload module set' );
111 $status = $this->mUpload->fetchFile();
112 if ( !$status->isGood() ) {
122 if ( !$this->mParams[
'stash'] ) {
123 $permErrors = $this->mUpload->verifyTitlePermissions( $user );
124 if ( $permErrors !==
true ) {
125 $this->dieRecoverableError( $permErrors,
'filename' );
131 $result = $this->getContextResult();
140 if ( $result[
'result'] ===
'Success' ) {
141 $imageinfo = $this->mUpload->getImageInfo( $this->
getResult() );
146 $this->mUpload->cleanupTempFile();
153 private function getContextResult() {
155 if ( $warnings && !$this->mParams[
'ignorewarnings'] ) {
157 return $this->getWarningsResult( $warnings );
158 } elseif ( $this->mParams[
'chunk'] ) {
160 return $this->getChunkResult( $warnings );
161 } elseif ( $this->mParams[
'stash'] ) {
163 return $this->getStashResult( $warnings );
181 private function getStashResult( $warnings ) {
183 $result[
'result'] =
'Success';
184 if ( $warnings && count( $warnings ) > 0 ) {
185 $result[
'warnings'] = $warnings;
189 $this->performStash(
'critical', $result );
199 private function getWarningsResult( $warnings ) {
201 $result[
'result'] =
'Warning';
202 $result[
'warnings'] = $warnings;
205 $this->performStash(
'optional', $result );
217 $configured = $config->
get( MainConfigNames::MinUploadChunkSize );
222 ini_get(
'post_max_size' ),
242 private function getChunkResult( $warnings ) {
245 if ( $warnings && count( $warnings ) > 0 ) {
246 $result[
'warnings'] = $warnings;
249 $request = $this->
getMain()->getRequest();
250 $chunkPath = $request->getFileTempname(
'chunk' );
251 $chunkSize = $request->getUpload(
'chunk' )->getSize();
252 $totalSoFar = $this->mParams[
'offset'] + $chunkSize;
256 if ( $totalSoFar > $this->mParams[
'filesize'] ) {
261 if ( $totalSoFar != $this->mParams[
'filesize'] && $chunkSize < $minChunkSize ) {
265 if ( $this->mParams[
'offset'] == 0 ) {
266 $filekey = $this->performStash(
'critical' );
268 $filekey = $this->mParams[
'filekey'];
274 $this->
dieWithError(
'apierror-stashfailed-nosession',
'stashfailed' );
275 } elseif ( $progress[
'result'] !==
'Continue' || $progress[
'stage'] !==
'uploading' ) {
276 $this->
dieWithError(
'apierror-stashfailed-complete',
'stashfailed' );
279 $status = $this->mUpload->addChunk(
280 $chunkPath, $chunkSize, $this->mParams[
'offset'] );
281 if ( !$status->isGood() ) {
283 'offset' => $this->mUpload->getOffset(),
291 if ( $totalSoFar == $this->mParams[
'filesize'] ) {
292 if ( $this->mParams[
'async'] ) {
296 [
'result' =>
'Poll',
297 'stage' =>
'queued',
'status' => Status::newGood() ]
300 Title::makeTitle(
NS_FILE, $filekey ),
302 'filename' => $this->mParams[
'filename'],
303 'filekey' => $filekey,
307 $result[
'result'] =
'Poll';
308 $result[
'stage'] =
'queued';
310 $status = $this->mUpload->concatenateChunks();
311 if ( !$status->isGood() ) {
315 [
'result' =>
'Failure',
'stage' =>
'assembling',
'status' => $status ]
323 $result[
'warnings'] = $warnings;
329 $this->mUpload->stash->removeFile( $filekey );
330 $filekey = $this->mUpload->getStashFile()->getFileKey();
332 $result[
'result'] =
'Success';
339 'result' =>
'Continue',
340 'stage' =>
'uploading',
341 'offset' => $totalSoFar,
342 'status' => Status::newGood(),
345 $result[
'result'] =
'Continue';
346 $result[
'offset'] = $totalSoFar;
349 $result[
'filekey'] = $filekey;
366 private function performStash( $failureMode, &$data =
null ) {
367 $isPartial = (bool)$this->mParams[
'chunk'];
369 $status = $this->mUpload->tryStashFile( $this->
getUser(), $isPartial );
371 if ( $status->isGood() && !$status->getValue() ) {
373 $status->fatal(
new ApiMessage(
'apierror-stashinvalidfile',
'stashfailed' ) );
375 }
catch ( Exception $e ) {
376 $debugMessage =
'Stashing temporary file failed: ' . get_class( $e ) .
' ' . $e->getMessage();
377 wfDebug( __METHOD__ .
' ' . $debugMessage );
379 $e, [
'wrap' =>
new ApiMessage(
'apierror-stashexception',
'stashfailed' ) ]
383 if ( $status->isGood() ) {
384 $stashFile = $status->getValue();
385 $data[
'filekey'] = $stashFile->getFileKey();
387 $data[
'sessionkey'] = $data[
'filekey'];
388 return $data[
'filekey'];
391 if ( $status->getMessage()->getKey() ===
'uploadstash-exception' ) {
394 [ $exceptionType, $message ] = $status->getMessage()->getParams();
395 $debugMessage =
'Stashing temporary file failed: ' . $exceptionType .
' ' . $message;
396 wfDebug( __METHOD__ .
' ' . $debugMessage );
400 if ( $failureMode !==
'optional' ) {
418 private function dieRecoverableError( $errors, $parameter =
null ) {
419 $this->performStash(
'optional', $data );
422 $data[
'invalidparameter'] = $parameter;
426 foreach ( $errors as $error ) {
428 $msg->setApiData( $msg->getApiData() + $data );
446 foreach ( $status->getErrors() as $error ) {
448 if ( $moreExtraData ) {
449 $msg->setApiData( $msg->getApiData() + $moreExtraData );
465 $request = $this->
getMain()->getRequest();
468 if ( !$this->mParams[
'chunk'] ) {
470 'filekey',
'file',
'url' );
474 if ( $this->mParams[
'filekey'] && $this->mParams[
'checkstatus'] ) {
477 $this->
dieWithError(
'apierror-upload-missingresult',
'missingresult' );
478 } elseif ( !$progress[
'status']->isGood() ) {
481 if ( isset( $progress[
'status']->value[
'verification'] ) ) {
484 if ( isset( $progress[
'status']->value[
'warnings'] ) ) {
487 $progress[
'warnings'] = $warnings;
490 unset( $progress[
'status'] );
492 if ( isset( $progress[
'imageinfo'] ) ) {
493 $imageinfo = $progress[
'imageinfo'];
494 unset( $progress[
'imageinfo'] );
508 if ( $this->mParams[
'filename'] ===
null ) {
509 $this->
dieWithError( [
'apierror-missingparam',
'filename' ] );
512 if ( $this->mParams[
'chunk'] ) {
515 if ( isset( $this->mParams[
'filekey'] ) ) {
516 if ( $this->mParams[
'offset'] === 0 ) {
517 $this->
dieWithError(
'apierror-upload-filekeynotallowed',
'filekeynotallowed' );
521 $this->mUpload->continueChunks(
522 $this->mParams[
'filename'],
523 $this->mParams[
'filekey'],
524 $request->getUpload(
'chunk' )
527 if ( $this->mParams[
'offset'] !== 0 ) {
528 $this->
dieWithError(
'apierror-upload-filekeyneeded',
'filekeyneeded' );
532 $this->mUpload->initialize(
533 $this->mParams[
'filename'],
534 $request->getUpload(
'chunk' )
537 } elseif ( isset( $this->mParams[
'filekey'] ) ) {
546 $this->mUpload->initialize(
547 $this->mParams[
'filekey'], $this->mParams[
'filename'], !$this->mParams[
'async']
549 } elseif ( isset( $this->mParams[
'file'] ) ) {
553 if ( $this->mParams[
'async'] ) {
554 $this->
dieWithError(
'apierror-cannot-async-upload-file' );
558 $this->mUpload->initialize(
559 $this->mParams[
'filename'],
560 $request->getUpload(
'file' )
562 } elseif ( isset( $this->mParams[
'url'] ) ) {
577 $this->mUpload->
initialize( $this->mParams[
'filename'],
578 $this->mParams[
'url'] );
591 $permission = $this->mUpload->isAllowed( $user );
593 if ( $permission !==
true ) {
594 if ( !$user->isNamed() ) {
595 $this->
dieWithError( [
'apierror-mustbeloggedin', $this->
msg(
'action-upload' ) ] );
598 $this->
dieStatus( User::newFatalPermissionDeniedStatus( $permission ) );
602 if ( $user->isBlockedFromUpload() ) {
612 if ( $this->mParams[
'chunk'] ) {
614 if ( $this->mParams[
'filesize'] > $maxSize ) {
617 if ( !$this->mUpload->getTitle() ) {
622 $verification = $this->mUpload->validateName();
623 if ( $verification ===
true ) {
626 } elseif ( $this->mParams[
'async'] && $this->mParams[
'filekey'] ) {
630 $verification = $this->mUpload->validateName();
631 if ( $verification ===
true ) {
635 wfDebug( __METHOD__ .
" about to verify" );
637 $verification = $this->mUpload->verifyUpload();
652 switch ( $verification[
'status'] ) {
655 $this->dieRecoverableError( [
'filename-tooshort' ],
'filename' );
658 $this->dieRecoverableError(
660 'illegal-filename',
null, [
'filename' => $verification[
'filtered'] ]
665 $this->dieRecoverableError( [
'filename-toolong' ],
'filename' );
668 $this->dieRecoverableError( [
'filetype-missing' ],
'filename' );
671 $this->dieRecoverableError( [
'windows-nonascii-filename' ],
'filename' );
683 'filetype' => $verification[
'finalExt'],
684 'allowed' => array_values( array_unique(
685 $this->
getConfig()->
get( MainConfigNames::FileExtensions ) ) )
688 array_unique( $this->
getConfig()->
get( MainConfigNames::FileExtensions ) );
690 'filetype-banned-type',
693 count( $extensions ),
698 if ( isset( $verification[
'blacklistedExt'] ) ) {
700 $msg[4] = count( $verification[
'blacklistedExt'] );
701 $extradata[
'blacklisted'] = array_values( $verification[
'blacklistedExt'] );
704 $msg[1] = $verification[
'finalExt'];
708 $this->
dieWithError( $msg,
'filetype-banned', $extradata );
714 $details = array_merge( [ $msg->getKey() ], $msg->getParams() );
716 $details = $verification[
'details'];
719 $msg->setApiData( $msg->getApiData() + [
'details' => $details ] );
725 $msg = $verification[
'error'] ===
'' ?
'hookaborted' : $verification[
'error'];
726 $this->
dieWithError( $msg,
'hookaborted', [
'details' => $verification[
'error'] ] );
729 $this->
dieWithError(
'apierror-unknownerror-nocode',
'unknown-error',
730 [
'details' => [
'code' => $verification[
'status'] ] ] );
743 $this->mUpload->checkWarnings( $this->getUser() )
754 if ( isset( $warnings[
'duplicate'] ) ) {
755 $dupes = array_column( $warnings[
'duplicate'],
'fileName' );
757 $warnings[
'duplicate'] = $dupes;
760 if ( isset( $warnings[
'exists'] ) ) {
761 $warning = $warnings[
'exists'];
762 unset( $warnings[
'exists'] );
763 $localFile = $warning[
'normalizedFile'] ?? $warning[
'file'];
764 $warnings[$warning[
'warning']] = $localFile[
'fileName'];
767 if ( isset( $warnings[
'no-change'] ) ) {
768 $file = $warnings[
'no-change'];
769 unset( $warnings[
'no-change'] );
771 $warnings[
'nochange'] = [
776 if ( isset( $warnings[
'duplicate-version'] ) ) {
778 foreach ( $warnings[
'duplicate-version'] as $dupe ) {
780 'timestamp' =>
wfTimestamp( TS_ISO_8601, $dupe[
'timestamp'] )
783 unset( $warnings[
'duplicate-version'] );
786 $warnings[
'duplicateversions'] = $dupes;
800 switch ( get_class( $e ) ) {
801 case UploadStashFileNotFoundException::class:
802 $wrap =
'apierror-stashedfilenotfound';
804 case UploadStashBadPathException::class:
805 $wrap =
'apierror-stashpathinvalid';
807 case UploadStashFileException::class:
808 $wrap =
'apierror-stashfilestorage';
810 case UploadStashZeroLengthFileException::class:
811 $wrap =
'apierror-stashzerolength';
813 case UploadStashNotLoggedInException::class:
815 [
'apierror-mustbeloggedin', $this->
msg(
'action-upload' ) ],
'stashnotloggedin'
817 case UploadStashWrongOwnerException::class:
818 $wrap =
'apierror-stashwrongowner';
820 case UploadStashNoSuchKeyException::class:
821 $wrap =
'apierror-stashnosuchfilekey';
824 $wrap = [
'uploadstash-exception', get_class( $e ) ];
841 $this->mParams[
'text'] ??= $this->mParams[
'comment'];
844 $file = $this->mUpload->getLocalFile();
846 $title =
$file->getTitle();
854 $this->mParams[
'watchlist'], $title, $user,
'watchdefault'
857 if ( !$watch && $this->mParams[
'watchlist'] ==
'preferences' && !
$file->exists() ) {
866 if ( $this->mParams[
'watch'] ) {
870 if ( $this->mParams[
'tags'] ) {
872 if ( !$status->isOK() ) {
879 if ( $this->mParams[
'async'] ) {
881 if ( $progress && $progress[
'result'] ===
'Poll' ) {
882 $this->
dieWithError(
'apierror-upload-inprogress',
'publishfailed' );
886 $this->mParams[
'filekey'],
887 [
'result' =>
'Poll',
'stage' =>
'queued',
'status' => Status::newGood() ]
890 Title::makeTitle(
NS_FILE, $this->mParams[
'filename'] ),
892 'filename' => $this->mParams[
'filename'],
893 'filekey' => $this->mParams[
'filekey'],
894 'comment' => $this->mParams[
'comment'],
895 'tags' => $this->mParams[
'tags'] ?? [],
896 'text' => $this->mParams[
'text'],
898 'watchlistexpiry' => $watchlistExpiry,
902 $result[
'result'] =
'Poll';
903 $result[
'stage'] =
'queued';
906 $status = $this->mUpload->performUpload(
907 $this->mParams[
'comment'],
908 $this->mParams[
'text'],
911 $this->mParams[
'tags'] ?? [],
915 if ( !$status->isGood() ) {
916 $this->dieRecoverableError( $status->getErrors() );
918 $result[
'result'] =
'Success';
921 $result[
'filename'] =
$file->getName();
922 if ( $warnings && count( $warnings ) > 0 ) {
923 $result[
'warnings'] = $warnings;
940 ParamValidator::PARAM_TYPE =>
'string',
943 ParamValidator::PARAM_DEFAULT =>
''
946 ParamValidator::PARAM_TYPE =>
'tags',
947 ParamValidator::PARAM_ISMULTI =>
true,
950 ParamValidator::PARAM_TYPE =>
'text',
953 ParamValidator::PARAM_DEFAULT =>
false,
954 ParamValidator::PARAM_DEPRECATED =>
true,
967 'ignorewarnings' =>
false,
969 ParamValidator::PARAM_TYPE =>
'upload',
974 ParamValidator::PARAM_DEPRECATED =>
true,
979 ParamValidator::PARAM_TYPE =>
'integer',
980 IntegerDef::PARAM_MIN => 0,
984 ParamValidator::PARAM_TYPE =>
'integer',
985 IntegerDef::PARAM_MIN => 0,
988 ParamValidator::PARAM_TYPE =>
'upload',
992 'checkstatus' =>
false,
1004 'action=upload&filename=Wiki.png' .
1005 '&url=http%3A//upload.wikimedia.org/wikipedia/en/b/bc/Wiki.png&token=123ABC'
1006 =>
'apihelp-upload-example-url',
1007 'action=upload&filename=Wiki.png&filekey=filekey&ignorewarnings=1&token=123ABC'
1008 =>
'apihelp-upload-example-filekey',
1013 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.
requireOnlyOneParameter( $params,... $required)
Die if 0 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.
static create( $msg, $code=null, array $data=null)
Create an IApiMessage for the message.
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
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.
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)
getExamplesMessages()
Returns usage examples for this module.
selectUploadModule()
Select an upload module and set it to mUpload.
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.
Handle enqueueing of background jobs.
A class containing constants representing the names of configuration variables.
static listParam(array $list, $type='text')
Upload a file from the upload stash into the local file repo.
static newFatal( $message,... $parameters)
Factory function for fatal errors.
static newGood( $value=null)
Factory function for good results.
static makeWarningsSerializable( $warnings)
Convert the warnings array returned by checkWarnings() to something that can be serialized.
static setSessionStatus(UserIdentity $user, $statusKey, $value)
Set the current status of a chunked upload (used for polling).
static isEnabled()
Returns true if uploads are enabled.
static getSessionStatus(UserIdentity $user, $statusKey)
Get the current status of a chunked upload (used for polling).
const WINDOWS_NONASCII_FILENAME
static getMaxUploadSize( $forType=null)
Get MediaWiki's maximum uploaded file size for a given type of upload, based on $wgMaxUploadSize.
static isThrottled( $user)
Returns true if the user has surpassed the upload rate limit, false otherwise.
const MIN_LENGTH_PARTNAME
static getMaxPhpUploadSize()
Get the PHP maximum uploaded file size, based on ini settings.
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.
trait ApiWatchlistTrait
An ApiWatchlistTrait adds class properties and convenience methods for APIs that allow you to watch a...
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.