36use Psr\Log\LoggerInterface;
54 private LoggerInterface $log;
70 parent::__construct( $mainModule, $moduleName );
71 $this->jobQueueGroup = $jobQueueGroup;
74 $this->watchlistExpiryEnabled = $this->
getConfig()->
get( MainConfigNames::WatchlistExpiry );
75 $this->watchlistMaxDuration =
76 $this->
getConfig()->get( MainConfigNames::WatchlistExpiryMaxDuration );
77 $this->watchlistManager = $watchlistManager;
78 $this->userOptionsLookup = $userOptionsLookup;
79 $this->log = LoggerFactory::getInstance(
'upload' );
84 if ( !UploadBase::isEnabled() ) {
92 $request = $this->
getMain()->getRequest();
94 $this->mParams[
'async'] = ( $this->mParams[
'async'] &&
95 $this->
getConfig()->get( MainConfigNames::EnableAsyncUploads ) );
97 $this->mParams[
'file'] = $request->getFileName(
'file' );
98 $this->mParams[
'chunk'] = $request->getFileName(
'chunk' );
101 if ( !$this->mParams[
'filekey'] && $this->mParams[
'sessionkey'] ) {
102 $this->mParams[
'filekey'] = $this->mParams[
'sessionkey'];
105 if ( !$this->mParams[
'checkstatus'] ) {
113 } elseif ( !isset( $this->mUpload ) ) {
114 $this->
dieDebug( __METHOD__,
'No upload module set' );
126 if ( $this->mParams[
'async'] && $this->mParams[
'url'] ) {
127 $status = $this->mUpload->canFetchFile();
129 $status = $this->mUpload->fetchFile();
132 if ( !$status->isGood() ) {
133 $this->log->info(
"Unable to fetch file {filename} for {user} because {status}",
135 'user' => $this->
getUser()->getName(),
136 'status' => (
string)$status,
137 'filename' => $this->mParams[
'filename'] ??
'-',
149 if ( !$this->mParams[
'stash'] ) {
150 $permErrors = $this->mUpload->verifyTitlePermissions( $user );
151 if ( $permErrors !==
true ) {
152 $this->dieRecoverableError( $permErrors,
'filename' );
158 $result = $this->getContextResult();
167 if ( $result[
'result'] ===
'Success' ) {
173 $this->mUpload->cleanupTempFile();
182 $services->getJobQueueGroup(),
183 $services->getWatchlistManager(),
184 $services->getUserOptionsLookup()
205 $result = $this->getResult();
214 array_fill_keys( $imParam,
true ),
222 array_fill_keys( $imParam,
true ),
234 private function getContextResult() {
235 $warnings = $this->getApiWarnings();
236 if ( $warnings && !$this->mParams[
'ignorewarnings'] ) {
238 return $this->getWarningsResult( $warnings );
239 } elseif ( $this->mParams[
'chunk'] ) {
241 return $this->getChunkResult( $warnings );
242 } elseif ( $this->mParams[
'stash'] ) {
244 return $this->getStashResult( $warnings );
249 return $this->performUpload( $warnings );
257 private function getStashResult( $warnings ) {
259 $result[
'result'] =
'Success';
260 if ( $warnings && count( $warnings ) > 0 ) {
261 $result[
'warnings'] = $warnings;
265 $this->performStash(
'critical', $result );
275 private function getWarningsResult( $warnings ) {
277 $result[
'result'] =
'Warning';
278 $result[
'warnings'] = $warnings;
281 $this->performStash(
'optional', $result );
293 $configured = $config->
get( MainConfigNames::MinUploadChunkSize );
298 ini_get(
'post_max_size' ),
307 UploadBase::getMaxUploadSize(
'file' ),
308 UploadBase::getMaxPhpUploadSize(),
318 private function getChunkResult( $warnings ) {
321 if ( $warnings && count( $warnings ) > 0 ) {
322 $result[
'warnings'] = $warnings;
325 $request = $this->getMain()->getRequest();
326 $chunkPath = $request->getFileTempname(
'chunk' );
327 $chunkSize = $request->getUpload(
'chunk' )->getSize();
328 $totalSoFar = $this->mParams[
'offset'] + $chunkSize;
329 $minChunkSize = self::getMinUploadChunkSize( $this->getConfig() );
332 if ( $totalSoFar > $this->mParams[
'filesize'] ) {
333 $this->dieWithError(
'apierror-invalid-chunk' );
337 if ( $totalSoFar != $this->mParams[
'filesize'] && $chunkSize < $minChunkSize ) {
338 $this->dieWithError( [
'apierror-chunk-too-small', Message::numParam( $minChunkSize ) ] );
341 if ( $this->mParams[
'offset'] == 0 ) {
342 $this->log->debug(
"Started first chunk of chunked upload of {filename} for {user}",
344 'user' => $this->
getUser()->getName(),
345 'filename' => $this->mParams[
'filename'] ??
'-',
346 'filesize' => $this->mParams[
'filesize'],
347 'chunkSize' => $chunkSize
350 $filekey = $this->performStash(
'critical' );
352 $filekey = $this->mParams[
'filekey'];
358 $this->log->info(
"Stash failed due to no session for {user}",
360 'user' => $this->
getUser()->getName(),
361 'filename' => $this->mParams[
'filename'] ??
'-',
362 'filekey' => $this->mParams[
'filekey'] ??
'-',
363 'filesize' => $this->mParams[
'filesize'],
364 'chunkSize' => $chunkSize
367 $this->dieWithError(
'apierror-stashfailed-nosession',
'stashfailed' );
368 } elseif ( $progress[
'result'] !==
'Continue' || $progress[
'stage'] !==
'uploading' ) {
369 $this->dieWithError(
'apierror-stashfailed-complete',
'stashfailed' );
372 $status = $this->mUpload->addChunk(
373 $chunkPath, $chunkSize, $this->mParams[
'offset'] );
374 if ( !$status->isGood() ) {
376 'offset' => $this->mUpload->getOffset(),
378 $this->log->info(
"Chunked upload stash failure {status} for {user}",
380 'status' => (
string)$status,
381 'user' => $this->
getUser()->getName(),
382 'filename' => $this->mParams[
'filename'] ??
'-',
383 'filekey' => $this->mParams[
'filekey'] ??
'-',
384 'filesize' => $this->mParams[
'filesize'],
385 'chunkSize' => $chunkSize,
386 'offset' => $this->mUpload->getOffset()
389 $this->dieStatusWithCode( $status,
'stashfailed', $extradata );
391 $this->log->debug(
"Got chunk for {filename} with offset {offset} for {user}",
393 'user' => $this->
getUser()->getName(),
394 'filename' => $this->mParams[
'filename'] ??
'-',
395 'filekey' => $this->mParams[
'filekey'] ??
'-',
396 'filesize' => $this->mParams[
'filesize'],
397 'chunkSize' => $chunkSize,
398 'offset' => $this->mUpload->getOffset()
405 if ( $totalSoFar == $this->mParams[
'filesize'] ) {
406 if ( $this->mParams[
'async'] ) {
410 [
'result' =>
'Poll',
411 'stage' =>
'queued',
'status' => Status::newGood() ]
418 'filename' => $this->mParams[
'filename'],
419 'filekey' => $filekey,
420 'filesize' => $this->mParams[
'filesize'],
421 'session' => $this->
getContext()->exportSession()
423 $this->log->info(
"Received final chunk of {filename} for {user}, queuing assemble job",
425 'user' => $this->
getUser()->getName(),
426 'filename' => $this->mParams[
'filename'] ??
'-',
427 'filekey' => $this->mParams[
'filekey'] ??
'-',
428 'filesize' => $this->mParams[
'filesize'],
429 'chunkSize' => $chunkSize,
432 $result[
'result'] =
'Poll';
433 $result[
'stage'] =
'queued';
435 $this->log->info(
"Received final chunk of {filename} for {user}, assembling immediately",
437 'user' => $this->
getUser()->getName(),
438 'filename' => $this->mParams[
'filename'] ??
'-',
439 'filekey' => $this->mParams[
'filekey'] ??
'-',
440 'filesize' => $this->mParams[
'filesize'],
441 'chunkSize' => $chunkSize,
445 $status = $this->mUpload->concatenateChunks();
446 if ( !$status->isGood() ) {
450 [
'result' =>
'Failure',
'stage' =>
'assembling',
'status' => $status ]
452 $this->log->info(
"Non jobqueue assembly of {filename} failed because {status}",
454 'user' => $this->
getUser()->getName(),
455 'filename' => $this->mParams[
'filename'] ??
'-',
456 'filekey' => $this->mParams[
'filekey'] ??
'-',
457 'filesize' => $this->mParams[
'filesize'],
458 'chunkSize' => $chunkSize,
459 'status' => (
string)$status
462 $this->dieStatusWithCode( $status,
'stashfailed' );
466 $warnings = $this->getApiWarnings();
468 $result[
'warnings'] = $warnings;
474 $this->mUpload->stash->removeFile( $filekey );
475 $filekey = $this->mUpload->getStashFile()->getFileKey();
477 $result[
'result'] =
'Success';
484 'result' =>
'Continue',
485 'stage' =>
'uploading',
486 'offset' => $totalSoFar,
487 'status' => Status::newGood(),
490 $result[
'result'] =
'Continue';
491 $result[
'offset'] = $totalSoFar;
494 $result[
'filekey'] = $filekey;
511 private function performStash( $failureMode, &$data =
null ) {
512 $isPartial = (bool)$this->mParams[
'chunk'];
514 $status = $this->mUpload->tryStashFile( $this->
getUser(), $isPartial );
516 if ( $status->isGood() && !$status->getValue() ) {
518 $status->fatal(
new ApiMessage(
'apierror-stashinvalidfile',
'stashfailed' ) );
520 }
catch ( Exception $e ) {
521 $debugMessage =
'Stashing temporary file failed: ' . get_class( $e ) .
' ' . $e->getMessage();
522 $this->log->info( $debugMessage,
524 'user' => $this->
getUser()->getName(),
525 'filename' => $this->mParams[
'filename'] ??
'-',
526 'filekey' => $this->mParams[
'filekey'] ??
'-'
530 $status = Status::newFatal( $this->getErrorFormatter()->getMessageFromException(
531 $e, [
'wrap' =>
new ApiMessage(
'apierror-stashexception',
'stashfailed' ) ]
535 if ( $status->isGood() ) {
536 $stashFile = $status->getValue();
537 $data[
'filekey'] = $stashFile->getFileKey();
539 $data[
'sessionkey'] = $data[
'filekey'];
540 return $data[
'filekey'];
543 if ( $status->getMessage()->getKey() ===
'uploadstash-exception' ) {
546 [ $exceptionType, $message ] = $status->getMessage()->getParams();
547 $debugMessage =
'Stashing temporary file failed: ' . $exceptionType .
' ' . $message;
548 $this->log->info( $debugMessage,
550 'user' => $this->
getUser()->getName(),
551 'filename' => $this->mParams[
'filename'] ??
'-',
552 'filekey' => $this->mParams[
'filekey'] ??
'-'
557 $this->log->info(
"Stash upload failure {status}",
559 'status' => (
string)$status,
560 'user' => $this->
getUser()->getName(),
561 'filename' => $this->mParams[
'filename'] ??
'-',
562 'filekey' => $this->mParams[
'filekey'] ??
'-'
566 if ( $failureMode !==
'optional' ) {
567 $this->dieStatus( $status );
569 $data[
'stasherrors'] = $this->getErrorFormatter()->arrayFromStatus( $status );
584 private function dieRecoverableError( $errors, $parameter =
null ) {
585 $this->performStash(
'optional', $data );
588 $data[
'invalidparameter'] = $parameter;
592 foreach ( $errors as $error ) {
594 $msg->setApiData( $msg->getApiData() + $data );
597 $this->dieStatus( $sv );
611 $sv = StatusValue::newGood();
612 foreach ( $status->getErrors() as $error ) {
613 $msg = ApiMessage::create( $error, $overrideCode );
614 if ( $moreExtraData ) {
615 $msg->setApiData( $msg->getApiData() + $moreExtraData );
619 $this->dieStatus( $sv );
630 $request = $this->getMain()->getRequest();
633 if ( !$this->mParams[
'chunk'] ) {
634 $this->requireOnlyOneParameter( $this->mParams,
635 'filekey',
'file',
'url' );
639 if ( $this->mParams[
'checkstatus'] && ( $this->mParams[
'filekey'] || $this->mParams[
'url'] ) ) {
641 $progress = UploadBase::getSessionStatus( $this->
getUser(), $statusKey );
643 $this->log->info(
"Cannot check upload status due to missing upload session for {user}",
645 'user' => $this->
getUser()->getName(),
646 'filename' => $this->mParams[
'filename'] ??
'-',
647 'filekey' => $this->mParams[
'filekey'] ??
'-'
650 $this->dieWithError(
'apierror-upload-missingresult',
'missingresult' );
651 } elseif ( !$progress[
'status']->isGood() ) {
652 $this->dieStatusWithCode( $progress[
'status'],
'stashfailed' );
654 if ( isset( $progress[
'status']->value[
'verification'] ) ) {
655 $this->checkVerification( $progress[
'status']->value[
'verification'] );
657 if ( isset( $progress[
'status']->value[
'warnings'] ) ) {
658 $warnings = $this->transformWarnings( $progress[
'status']->value[
'warnings'] );
660 $progress[
'warnings'] = $warnings;
663 unset( $progress[
'status'] );
665 if ( isset( $progress[
'imageinfo'] ) ) {
666 $imageinfo = $progress[
'imageinfo'];
667 unset( $progress[
'imageinfo'] );
670 $this->getResult()->addValue(
null, $this->getModuleName(), $progress );
674 $this->getResult()->addValue( $this->getModuleName(),
'imageinfo', $imageinfo );
681 if ( $this->mParams[
'filename'] ===
null ) {
682 $this->dieWithError( [
'apierror-missingparam',
'filename' ] );
685 if ( $this->mParams[
'chunk'] ) {
688 if ( isset( $this->mParams[
'filekey'] ) ) {
689 if ( $this->mParams[
'offset'] === 0 ) {
690 $this->dieWithError(
'apierror-upload-filekeynotallowed',
'filekeynotallowed' );
694 $this->mUpload->continueChunks(
695 $this->mParams[
'filename'],
696 $this->mParams[
'filekey'],
697 $request->getUpload(
'chunk' )
700 if ( $this->mParams[
'offset'] !== 0 ) {
701 $this->dieWithError(
'apierror-upload-filekeyneeded',
'filekeyneeded' );
705 $this->mUpload->initialize(
706 $this->mParams[
'filename'],
707 $request->getUpload(
'chunk' )
710 } elseif ( isset( $this->mParams[
'filekey'] ) ) {
712 if ( !UploadFromStash::isValidKey( $this->mParams[
'filekey'] ) ) {
713 $this->dieWithError(
'apierror-invalid-file-key' );
719 $this->mUpload->initialize(
720 $this->mParams[
'filekey'], $this->mParams[
'filename'], !$this->mParams[
'async']
722 } elseif ( isset( $this->mParams[
'file'] ) ) {
726 if ( $this->mParams[
'async'] ) {
727 $this->dieWithError(
'apierror-cannot-async-upload-file' );
731 $this->mUpload->initialize(
732 $this->mParams[
'filename'],
733 $request->getUpload(
'file' )
735 } elseif ( isset( $this->mParams[
'url'] ) ) {
738 $this->dieWithError(
'copyuploaddisabled' );
742 $this->dieWithError(
'apierror-copyuploadbaddomain' );
746 $this->dieWithError(
'apierror-copyuploadbadurl' );
750 $this->mUpload->
initialize( $this->mParams[
'filename'],
751 $this->mParams[
'url'] );
764 $permission = $this->mUpload->isAllowed( $user );
766 if ( $permission !==
true ) {
767 if ( !$user->isNamed() ) {
768 $this->dieWithError( [
'apierror-mustbeloggedin', $this->msg(
'action-upload' ) ] );
771 $this->dieStatus( User::newFatalPermissionDeniedStatus( $permission ) );
775 if ( $user->isBlockedFromUpload() ) {
777 $this->dieBlocked( $user->getBlock() );
785 if ( $this->mParams[
'chunk'] ) {
786 $maxSize = UploadBase::getMaxUploadSize();
787 if ( $this->mParams[
'filesize'] > $maxSize ) {
788 $this->dieWithError(
'file-too-large' );
790 if ( !$this->mUpload->getTitle() ) {
791 $this->dieWithError(
'illegal-filename' );
795 $verification = $this->mUpload->validateName();
796 if ( $verification ===
true ) {
799 } elseif ( $this->mParams[
'async'] && ( $this->mParams[
'filekey'] || $this->mParams[
'url'] ) ) {
803 $verification = $this->mUpload->validateName();
804 if ( $verification ===
true ) {
808 wfDebug( __METHOD__ .
" about to verify" );
810 $verification = $this->mUpload->verifyUpload();
812 if ( $verification[
'status'] === UploadBase::OK ) {
815 $this->log->info(
"File verification of {filename} failed for {user} because {result}",
817 'user' => $this->
getUser()->getName(),
818 'resultCode' => $verification[
'status'],
819 'result' => $this->mUpload->getVerificationErrorCode( $verification[
'status'] ),
820 'filename' => $this->mParams[
'filename'] ??
'-',
821 'details' => $verification[
'details'] ??
''
827 $this->checkVerification( $verification );
836 switch ( $verification[
'status'] ) {
838 case UploadBase::MIN_LENGTH_PARTNAME:
839 $this->dieRecoverableError( [
'filename-tooshort' ],
'filename' );
841 case UploadBase::ILLEGAL_FILENAME:
842 $this->dieRecoverableError(
843 [ ApiMessage::create(
844 'illegal-filename',
null, [
'filename' => $verification[
'filtered'] ]
848 case UploadBase::FILENAME_TOO_LONG:
849 $this->dieRecoverableError( [
'filename-toolong' ],
'filename' );
851 case UploadBase::FILETYPE_MISSING:
852 $this->dieRecoverableError( [
'filetype-missing' ],
'filename' );
854 case UploadBase::WINDOWS_NONASCII_FILENAME:
855 $this->dieRecoverableError( [
'windows-nonascii-filename' ],
'filename' );
858 case UploadBase::EMPTY_FILE:
859 $this->dieWithError(
'empty-file' );
861 case UploadBase::FILE_TOO_LARGE:
862 $this->dieWithError(
'file-too-large' );
865 case UploadBase::FILETYPE_BADTYPE:
867 'filetype' => $verification[
'finalExt'],
868 'allowed' => array_values( array_unique(
869 $this->getConfig()->
get( MainConfigNames::FileExtensions ) ) )
872 array_unique( $this->getConfig()->
get( MainConfigNames::FileExtensions ) );
874 'filetype-banned-type',
876 Message::listParam( $extensions,
'comma' ),
877 count( $extensions ),
880 ApiResult::setIndexedTagName( $extradata[
'allowed'],
'ext' );
882 if ( isset( $verification[
'blacklistedExt'] ) ) {
883 $msg[1] = Message::listParam( $verification[
'blacklistedExt'],
'comma' );
884 $msg[4] = count( $verification[
'blacklistedExt'] );
885 $extradata[
'blacklisted'] = array_values( $verification[
'blacklistedExt'] );
886 ApiResult::setIndexedTagName( $extradata[
'blacklisted'],
'ext' );
888 $msg[1] = $verification[
'finalExt'];
892 $this->dieWithError( $msg,
'filetype-banned', $extradata );
895 case UploadBase::VERIFICATION_ERROR:
896 $msg = ApiMessage::create( $verification[
'details'],
'verification-error' );
898 $details = [ $msg->getKey(), ...$msg->getParams() ];
900 $details = $verification[
'details'];
902 ApiResult::setIndexedTagName( $details,
'detail' );
903 $msg->setApiData( $msg->getApiData() + [
'details' => $details ] );
905 $this->dieWithError( $msg );
908 case UploadBase::HOOK_ABORTED:
909 $msg = $verification[
'error'] ===
'' ?
'hookaborted' : $verification[
'error'];
910 $this->dieWithError( $msg,
'hookaborted', [
'details' => $verification[
'error'] ] );
913 $this->dieWithError(
'apierror-unknownerror-nocode',
'unknown-error',
914 [
'details' => [
'code' => $verification[
'status'] ] ] );
926 $warnings = UploadBase::makeWarningsSerializable(
927 $this->mUpload->checkWarnings( $this->getUser() )
930 return $this->transformWarnings( $warnings );
936 ApiResult::setIndexedTagName( $warnings,
'warning' );
938 if ( isset( $warnings[
'duplicate'] ) ) {
939 $dupes = array_column( $warnings[
'duplicate'],
'fileName' );
940 ApiResult::setIndexedTagName( $dupes,
'duplicate' );
941 $warnings[
'duplicate'] = $dupes;
944 if ( isset( $warnings[
'exists'] ) ) {
945 $warning = $warnings[
'exists'];
946 unset( $warnings[
'exists'] );
947 $localFile = $warning[
'normalizedFile'] ?? $warning[
'file'];
948 $warnings[$warning[
'warning']] = $localFile[
'fileName'];
951 if ( isset( $warnings[
'no-change'] ) ) {
952 $file = $warnings[
'no-change'];
953 unset( $warnings[
'no-change'] );
955 $warnings[
'nochange'] = [
956 'timestamp' =>
wfTimestamp( TS_ISO_8601, $file[
'timestamp'] )
960 if ( isset( $warnings[
'duplicate-version'] ) ) {
962 foreach ( $warnings[
'duplicate-version'] as $dupe ) {
964 'timestamp' =>
wfTimestamp( TS_ISO_8601, $dupe[
'timestamp'] )
967 unset( $warnings[
'duplicate-version'] );
969 ApiResult::setIndexedTagName( $dupes,
'ver' );
970 $warnings[
'duplicateversions'] = $dupes;
973 if ( $this->mParams[
'async'] && $this->mParams[
'url'] ) {
974 unset( $warnings[
'empty-file'] );
988 $this->log->info(
"Upload stashing of {filename} failed for {user} because {error}",
990 'user' => $this->
getUser()->getName(),
991 'error' => get_class( $e ),
992 'filename' => $this->mParams[
'filename'] ??
'-',
993 'filekey' => $this->mParams[
'filekey'] ??
'-'
997 switch ( get_class( $e ) ) {
998 case UploadStashFileNotFoundException::class:
999 $wrap =
'apierror-stashedfilenotfound';
1001 case UploadStashBadPathException::class:
1002 $wrap =
'apierror-stashpathinvalid';
1004 case UploadStashFileException::class:
1005 $wrap =
'apierror-stashfilestorage';
1007 case UploadStashZeroLengthFileException::class:
1008 $wrap =
'apierror-stashzerolength';
1010 case UploadStashNotLoggedInException::class:
1011 return StatusValue::newFatal( ApiMessage::create(
1012 [
'apierror-mustbeloggedin', $this->msg(
'action-upload' ) ],
'stashnotloggedin'
1014 case UploadStashWrongOwnerException::class:
1015 $wrap =
'apierror-stashwrongowner';
1017 case UploadStashNoSuchKeyException::class:
1018 $wrap =
'apierror-stashnosuchfilekey';
1021 $wrap = [
'uploadstash-exception', get_class( $e ) ];
1024 return StatusValue::newFatal(
1025 $this->getErrorFormatter()->getMessageFromException( $e, [
'wrap' => $wrap ] )
1038 $this->mParams[
'text'] ??= $this->mParams[
'comment'];
1041 $file = $this->mUpload->getLocalFile();
1043 $title = $file->getTitle();
1051 $this->mParams[
'watchlist'], $title, $user,
'watchdefault'
1054 if ( !$watch && $this->mParams[
'watchlist'] ==
'preferences' && !$file->exists() ) {
1063 if ( $this->mParams[
'watch'] ) {
1067 if ( $this->mParams[
'tags'] ) {
1069 if ( !$status->isOK() ) {
1070 $this->dieStatus( $status );
1076 if ( $this->mParams[
'async'] ) {
1078 if ( $this->mParams[
'filekey'] ) {
1081 'filename' => $this->mParams[
'filename'],
1082 'filekey' => $this->mParams[
'filekey'],
1083 'comment' => $this->mParams[
'comment'],
1084 'tags' => $this->mParams[
'tags'] ?? [],
1085 'text' => $this->mParams[
'text'],
1087 'watchlistexpiry' => $watchlistExpiry,
1088 'session' => $this->
getContext()->exportSession(),
1089 'ignorewarnings' => $this->mParams[
'ignorewarnings']
1092 } elseif ( $this->mParams[
'url'] ) {
1095 'filename' => $this->mParams[
'filename'],
1096 'url' => $this->mParams[
'url'],
1097 'comment' => $this->mParams[
'comment'],
1098 'tags' => $this->mParams[
'tags'] ?? [],
1099 'text' => $this->mParams[
'text'],
1101 'watchlistexpiry' => $watchlistExpiry,
1102 'session' => $this->
getContext()->exportSession(),
1103 'ignorewarnings' => $this->mParams[
'ignorewarnings']
1107 $this->dieWithError(
'apierror-no-async-support',
'publishfailed' );
1113 $cacheKey =
$job->getCacheKey();
1116 $progress = UploadBase::getSessionStatus( $this->
getUser(), $cacheKey );
1117 if ( $progress && $progress[
'result'] ===
'Poll' ) {
1118 $this->dieWithError(
'apierror-upload-inprogress',
'publishfailed' );
1120 UploadBase::setSessionStatus(
1123 [
'result' =>
'Poll',
'stage' =>
'queued',
'status' => Status::newGood() ]
1126 $this->jobQueueGroup->push(
$job );
1127 $this->log->info(
"Sending publish job of {filename} for {user}",
1129 'user' => $this->
getUser()->getName(),
1130 'filename' => $this->mParams[
'filename'] ??
'-'
1133 $result[
'result'] =
'Poll';
1134 $result[
'stage'] =
'queued';
1137 $status = $this->mUpload->performUpload(
1138 $this->mParams[
'comment'],
1139 $this->mParams[
'text'],
1142 $this->mParams[
'tags'] ?? [],
1146 if ( !$status->isGood() ) {
1147 $this->log->info(
"Non-async API upload publish failed for {user} because {status}",
1149 'user' => $this->
getUser()->getName(),
1150 'filename' => $this->mParams[
'filename'] ??
'-',
1151 'filekey' => $this->mParams[
'filekey'] ??
'-',
1152 'status' => (
string)$status
1155 $this->dieRecoverableError( $status->getErrors() );
1157 $result[
'result'] =
'Success';
1160 $result[
'filename'] = $file->getName();
1161 if ( $warnings && count( $warnings ) > 0 ) {
1162 $result[
'warnings'] = $warnings;
1179 ParamValidator::PARAM_TYPE =>
'string',
1182 ParamValidator::PARAM_DEFAULT =>
''
1185 ParamValidator::PARAM_TYPE =>
'tags',
1186 ParamValidator::PARAM_ISMULTI =>
true,
1189 ParamValidator::PARAM_TYPE =>
'text',
1192 ParamValidator::PARAM_DEFAULT =>
false,
1193 ParamValidator::PARAM_DEPRECATED =>
true,
1206 'ignorewarnings' =>
false,
1208 ParamValidator::PARAM_TYPE =>
'upload',
1213 ParamValidator::PARAM_DEPRECATED =>
true,
1218 ParamValidator::PARAM_TYPE =>
'integer',
1219 IntegerDef::PARAM_MIN => 0,
1220 IntegerDef::PARAM_MAX => UploadBase::getMaxUploadSize(),
1223 ParamValidator::PARAM_TYPE =>
'integer',
1224 IntegerDef::PARAM_MIN => 0,
1227 ParamValidator::PARAM_TYPE =>
'upload',
1231 'checkstatus' =>
false,
1243 'action=upload&filename=Wiki.png' .
1244 '&url=http%3A//upload.wikimedia.org/wikipedia/en/b/bc/Wiki.png&token=123ABC'
1245 =>
'apihelp-upload-example-url',
1246 'action=upload&filename=Wiki.png&filekey=filekey&ignorewarnings=1&token=123ABC'
1247 =>
'apihelp-upload-example-filekey',
1252 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.
array $params
The job parameters.
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.
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.
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
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 getPropertyNames( $filter=[])
Returns all possible parameters to iiprop.
static getInfo( $file, $prop, $result, $thumbParams=null, $opts=false)
Get result information for an image revision.
static getPropertyNames( $filter=null)
Returns all possible parameters to siiprop.
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)
getUploadImageInfo(UploadBase $upload)
Gets image info about the file just uploaded.
__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.
static getDummyInstance()
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.
Handle enqueueing of background jobs.
get( $type)
Get the job queue object for a given queue type.
A class containing constants representing the names of configuration variables.
Upload a file from the upload stash into the local file repo.
static newGood( $value=null)
Factory function for good results.
UploadBase and subclasses are the backend of MediaWiki's file uploads.
static setSessionStatus(UserIdentity $user, $statusKey, $value)
Set the current status of a chunked upload (used for polling).
getLocalFile()
Return the local file and initializes if necessary.
static getSessionStatus(UserIdentity $user, $statusKey)
Get the current status of a chunked upload (used for polling).
Implements uploading from chunks.
Implements regular file uploads.
Implements uploading from previously stored file.
Upload a file by URL, via the jobqueue.
Implements uploading from a HTTP resource.
initialize( $name, $url)
Entry point for API upload.
static getCacheKey( $params)
Provides a caching key for an upload from url set of parameters Used to set the status of an async jo...
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(count( $args)< 1) $job