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() ) {
93 $this->mParams[
'async'] = ( $this->mParams[
'async'] &&
94 $this->
getConfig()->get( MainConfigNames::EnableAsyncUploads ) );
97 if ( !$this->mParams[
'filekey'] && $this->mParams[
'sessionkey'] ) {
98 $this->mParams[
'filekey'] = $this->mParams[
'sessionkey'];
101 if ( !$this->mParams[
'checkstatus'] ) {
109 } elseif ( !isset( $this->mUpload ) ) {
110 $this->
dieDebug( __METHOD__,
'No upload module set' );
122 if ( $this->mParams[
'async'] && $this->mParams[
'url'] ) {
123 $status = $this->mUpload->canFetchFile();
125 $status = $this->mUpload->fetchFile();
128 if ( !$status->isGood() ) {
129 $this->log->info(
"Unable to fetch file {filename} for {user} because {status}",
131 'user' => $this->
getUser()->getName(),
132 'status' => (
string)$status,
133 'filename' => $this->mParams[
'filename'] ??
'-',
145 if ( !$this->mParams[
'stash'] ) {
146 $permErrors = $this->mUpload->verifyTitlePermissions( $user );
147 if ( $permErrors !==
true ) {
148 $this->dieRecoverableError( $permErrors,
'filename' );
154 $result = $this->getContextResult();
163 if ( $result[
'result'] ===
'Success' ) {
169 $this->mUpload->cleanupTempFile();
178 $services->getJobQueueGroup(),
179 $services->getWatchlistManager(),
180 $services->getUserOptionsLookup()
201 $result = $this->getResult();
210 array_fill_keys( $imParam,
true ),
218 array_fill_keys( $imParam,
true ),
230 private function getContextResult() {
231 $warnings = $this->getApiWarnings();
232 if ( $warnings && !$this->mParams[
'ignorewarnings'] ) {
234 return $this->getWarningsResult( $warnings );
235 } elseif ( $this->mParams[
'chunk'] ) {
237 return $this->getChunkResult( $warnings );
238 } elseif ( $this->mParams[
'stash'] ) {
240 return $this->getStashResult( $warnings );
245 return $this->performUpload( $warnings );
253 private function getStashResult( $warnings ) {
255 $result[
'result'] =
'Success';
256 if ( $warnings && count( $warnings ) > 0 ) {
257 $result[
'warnings'] = $warnings;
261 $this->performStash(
'critical', $result );
271 private function getWarningsResult( $warnings ) {
273 $result[
'result'] =
'Warning';
274 $result[
'warnings'] = $warnings;
277 $this->performStash(
'optional', $result );
289 $configured = $config->
get( MainConfigNames::MinUploadChunkSize );
294 ini_get(
'post_max_size' ),
303 UploadBase::getMaxUploadSize(
'file' ),
304 UploadBase::getMaxPhpUploadSize(),
314 private function getChunkResult( $warnings ) {
317 if ( $warnings && count( $warnings ) > 0 ) {
318 $result[
'warnings'] = $warnings;
321 $chunkUpload = $this->getMain()->getUpload(
'chunk' );
322 $chunkPath = $chunkUpload->getTempName();
323 $chunkSize = $chunkUpload->getSize();
324 $totalSoFar = $this->mParams[
'offset'] + $chunkSize;
325 $minChunkSize = self::getMinUploadChunkSize( $this->getConfig() );
328 if ( $totalSoFar > $this->mParams[
'filesize'] ) {
329 $this->dieWithError(
'apierror-invalid-chunk' );
333 if ( $totalSoFar != $this->mParams[
'filesize'] && $chunkSize < $minChunkSize ) {
334 $this->dieWithError( [
'apierror-chunk-too-small', Message::numParam( $minChunkSize ) ] );
337 if ( $this->mParams[
'offset'] == 0 ) {
338 $this->log->debug(
"Started first chunk of chunked upload of {filename} for {user}",
340 'user' => $this->
getUser()->getName(),
341 'filename' => $this->mParams[
'filename'] ??
'-',
342 'filesize' => $this->mParams[
'filesize'],
343 'chunkSize' => $chunkSize
346 $filekey = $this->performStash(
'critical' );
348 $filekey = $this->mParams[
'filekey'];
354 $this->log->info(
"Stash failed due to no session for {user}",
356 'user' => $this->
getUser()->getName(),
357 'filename' => $this->mParams[
'filename'] ??
'-',
358 'filekey' => $this->mParams[
'filekey'] ??
'-',
359 'filesize' => $this->mParams[
'filesize'],
360 'chunkSize' => $chunkSize
363 $this->dieWithError(
'apierror-stashfailed-nosession',
'stashfailed' );
364 } elseif ( $progress[
'result'] !==
'Continue' || $progress[
'stage'] !==
'uploading' ) {
365 $this->dieWithError(
'apierror-stashfailed-complete',
'stashfailed' );
368 $status = $this->mUpload->addChunk(
369 $chunkPath, $chunkSize, $this->mParams[
'offset'] );
370 if ( !$status->isGood() ) {
372 'offset' => $this->mUpload->getOffset(),
374 $this->log->info(
"Chunked upload stash failure {status} for {user}",
376 'status' => (
string)$status,
377 'user' => $this->
getUser()->getName(),
378 'filename' => $this->mParams[
'filename'] ??
'-',
379 'filekey' => $this->mParams[
'filekey'] ??
'-',
380 'filesize' => $this->mParams[
'filesize'],
381 'chunkSize' => $chunkSize,
382 'offset' => $this->mUpload->getOffset()
385 $this->dieStatusWithCode( $status,
'stashfailed', $extradata );
387 $this->log->debug(
"Got chunk for {filename} with offset {offset} for {user}",
389 'user' => $this->
getUser()->getName(),
390 'filename' => $this->mParams[
'filename'] ??
'-',
391 'filekey' => $this->mParams[
'filekey'] ??
'-',
392 'filesize' => $this->mParams[
'filesize'],
393 'chunkSize' => $chunkSize,
394 'offset' => $this->mUpload->getOffset()
401 if ( $totalSoFar == $this->mParams[
'filesize'] ) {
402 if ( $this->mParams[
'async'] ) {
406 [
'result' =>
'Poll',
407 'stage' =>
'queued',
'status' => Status::newGood() ]
414 'filename' => $this->mParams[
'filename'],
415 'filekey' => $filekey,
416 'filesize' => $this->mParams[
'filesize'],
417 'session' => $this->
getContext()->exportSession()
419 $this->log->info(
"Received final chunk of {filename} for {user}, queuing assemble job",
421 'user' => $this->
getUser()->getName(),
422 'filename' => $this->mParams[
'filename'] ??
'-',
423 'filekey' => $this->mParams[
'filekey'] ??
'-',
424 'filesize' => $this->mParams[
'filesize'],
425 'chunkSize' => $chunkSize,
428 $result[
'result'] =
'Poll';
429 $result[
'stage'] =
'queued';
431 $this->log->info(
"Received final chunk of {filename} for {user}, assembling immediately",
433 'user' => $this->
getUser()->getName(),
434 'filename' => $this->mParams[
'filename'] ??
'-',
435 'filekey' => $this->mParams[
'filekey'] ??
'-',
436 'filesize' => $this->mParams[
'filesize'],
437 'chunkSize' => $chunkSize,
441 $status = $this->mUpload->concatenateChunks();
442 if ( !$status->isGood() ) {
446 [
'result' =>
'Failure',
'stage' =>
'assembling',
'status' => $status ]
448 $this->log->info(
"Non jobqueue assembly of {filename} failed because {status}",
450 'user' => $this->
getUser()->getName(),
451 'filename' => $this->mParams[
'filename'] ??
'-',
452 'filekey' => $this->mParams[
'filekey'] ??
'-',
453 'filesize' => $this->mParams[
'filesize'],
454 'chunkSize' => $chunkSize,
455 'status' => (
string)$status
458 $this->dieStatusWithCode( $status,
'stashfailed' );
462 $warnings = $this->getApiWarnings();
464 $result[
'warnings'] = $warnings;
470 $this->mUpload->stash->removeFile( $filekey );
471 $filekey = $this->mUpload->getStashFile()->getFileKey();
473 $result[
'result'] =
'Success';
480 'result' =>
'Continue',
481 'stage' =>
'uploading',
482 'offset' => $totalSoFar,
483 'status' => Status::newGood(),
486 $result[
'result'] =
'Continue';
487 $result[
'offset'] = $totalSoFar;
490 $result[
'filekey'] = $filekey;
507 private function performStash( $failureMode, &$data =
null ) {
508 $isPartial = (bool)$this->mParams[
'chunk'];
510 $status = $this->mUpload->tryStashFile( $this->
getUser(), $isPartial );
512 if ( $status->isGood() && !$status->getValue() ) {
514 $status->fatal(
new ApiMessage(
'apierror-stashinvalidfile',
'stashfailed' ) );
516 }
catch ( Exception $e ) {
517 $debugMessage =
'Stashing temporary file failed: ' . get_class( $e ) .
' ' . $e->getMessage();
518 $this->log->info( $debugMessage,
520 'user' => $this->
getUser()->getName(),
521 'filename' => $this->mParams[
'filename'] ??
'-',
522 'filekey' => $this->mParams[
'filekey'] ??
'-'
526 $status = Status::newFatal( $this->getErrorFormatter()->getMessageFromException(
527 $e, [
'wrap' =>
new ApiMessage(
'apierror-stashexception',
'stashfailed' ) ]
531 if ( $status->isGood() ) {
532 $stashFile = $status->getValue();
533 $data[
'filekey'] = $stashFile->getFileKey();
535 $data[
'sessionkey'] = $data[
'filekey'];
536 return $data[
'filekey'];
539 if ( $status->getMessage()->getKey() ===
'uploadstash-exception' ) {
542 [ $exceptionType, $message ] = $status->getMessage()->getParams();
543 $debugMessage =
'Stashing temporary file failed: ' . $exceptionType .
' ' . $message;
544 $this->log->info( $debugMessage,
546 'user' => $this->
getUser()->getName(),
547 'filename' => $this->mParams[
'filename'] ??
'-',
548 'filekey' => $this->mParams[
'filekey'] ??
'-'
553 $this->log->info(
"Stash upload failure {status}",
555 'status' => (
string)$status,
556 'user' => $this->
getUser()->getName(),
557 'filename' => $this->mParams[
'filename'] ??
'-',
558 'filekey' => $this->mParams[
'filekey'] ??
'-'
562 if ( $failureMode !==
'optional' ) {
563 $this->dieStatus( $status );
565 $data[
'stasherrors'] = $this->getErrorFormatter()->arrayFromStatus( $status );
580 private function dieRecoverableError( $errors, $parameter =
null ) {
581 $this->performStash(
'optional', $data );
584 $data[
'invalidparameter'] = $parameter;
588 foreach ( $errors as $error ) {
590 $msg->setApiData( $msg->getApiData() + $data );
593 $this->dieStatus( $sv );
607 $sv = StatusValue::newGood();
608 foreach ( $status->getErrors() as $error ) {
609 $msg = ApiMessage::create( $error, $overrideCode );
610 if ( $moreExtraData ) {
611 $msg->setApiData( $msg->getApiData() + $moreExtraData );
615 $this->dieStatus( $sv );
627 if ( !$this->mParams[
'chunk'] ) {
628 $this->requireOnlyOneParameter( $this->mParams,
629 'filekey',
'file',
'url' );
633 if ( $this->mParams[
'checkstatus'] && ( $this->mParams[
'filekey'] || $this->mParams[
'url'] ) ) {
635 $progress = UploadBase::getSessionStatus( $this->
getUser(), $statusKey );
637 $this->log->info(
"Cannot check upload status due to missing upload session for {user}",
639 'user' => $this->
getUser()->getName(),
640 'filename' => $this->mParams[
'filename'] ??
'-',
641 'filekey' => $this->mParams[
'filekey'] ??
'-'
644 $this->dieWithError(
'apierror-upload-missingresult',
'missingresult' );
645 } elseif ( !$progress[
'status']->isGood() ) {
646 $this->dieStatusWithCode( $progress[
'status'],
'stashfailed' );
648 if ( isset( $progress[
'status']->value[
'verification'] ) ) {
649 $this->checkVerification( $progress[
'status']->value[
'verification'] );
651 if ( isset( $progress[
'status']->value[
'warnings'] ) ) {
652 $warnings = $this->transformWarnings( $progress[
'status']->value[
'warnings'] );
654 $progress[
'warnings'] = $warnings;
657 unset( $progress[
'status'] );
659 if ( isset( $progress[
'imageinfo'] ) ) {
660 $imageinfo = $progress[
'imageinfo'];
661 unset( $progress[
'imageinfo'] );
664 $this->getResult()->addValue(
null, $this->getModuleName(), $progress );
668 $this->getResult()->addValue( $this->getModuleName(),
'imageinfo', $imageinfo );
675 if ( $this->mParams[
'filename'] ===
null ) {
676 $this->dieWithError( [
'apierror-missingparam',
'filename' ] );
679 if ( $this->mParams[
'chunk'] ) {
682 if ( isset( $this->mParams[
'filekey'] ) ) {
683 if ( $this->mParams[
'offset'] === 0 ) {
684 $this->dieWithError(
'apierror-upload-filekeynotallowed',
'filekeynotallowed' );
688 $this->mUpload->continueChunks(
689 $this->mParams[
'filename'],
690 $this->mParams[
'filekey'],
694 if ( $this->mParams[
'offset'] !== 0 ) {
695 $this->dieWithError(
'apierror-upload-filekeyneeded',
'filekeyneeded' );
699 $this->mUpload->initialize(
700 $this->mParams[
'filename'],
704 } elseif ( isset( $this->mParams[
'filekey'] ) ) {
706 if ( !UploadFromStash::isValidKey( $this->mParams[
'filekey'] ) ) {
707 $this->dieWithError(
'apierror-invalid-file-key' );
713 $this->mUpload->initialize(
714 $this->mParams[
'filekey'], $this->mParams[
'filename'], !$this->mParams[
'async']
716 } elseif ( isset( $this->mParams[
'file'] ) ) {
720 if ( $this->mParams[
'async'] ) {
721 $this->dieWithError(
'apierror-cannot-async-upload-file' );
725 $this->mUpload->initialize(
726 $this->mParams[
'filename'],
729 } elseif ( isset( $this->mParams[
'url'] ) ) {
732 $this->dieWithError(
'copyuploaddisabled' );
736 $this->dieWithError(
'apierror-copyuploadbaddomain' );
740 $this->dieWithError(
'apierror-copyuploadbadurl' );
744 $this->mUpload->
initialize( $this->mParams[
'filename'],
745 $this->mParams[
'url'] );
758 $permission = $this->mUpload->isAllowed( $user );
760 if ( $permission !==
true ) {
761 if ( !$user->isNamed() ) {
762 $this->dieWithError( [
'apierror-mustbeloggedin', $this->msg(
'action-upload' ) ] );
765 $this->dieStatus( User::newFatalPermissionDeniedStatus( $permission ) );
769 if ( $user->isBlockedFromUpload() ) {
771 $this->dieBlocked( $user->getBlock() );
779 if ( $this->mParams[
'chunk'] ) {
780 $maxSize = UploadBase::getMaxUploadSize();
781 if ( $this->mParams[
'filesize'] > $maxSize ) {
782 $this->dieWithError(
'file-too-large' );
784 if ( !$this->mUpload->getTitle() ) {
785 $this->dieWithError(
'illegal-filename' );
789 $verification = $this->mUpload->validateName();
790 if ( $verification ===
true ) {
793 } elseif ( $this->mParams[
'async'] && ( $this->mParams[
'filekey'] || $this->mParams[
'url'] ) ) {
797 $verification = $this->mUpload->validateName();
798 if ( $verification ===
true ) {
802 wfDebug( __METHOD__ .
" about to verify" );
804 $verification = $this->mUpload->verifyUpload();
806 if ( $verification[
'status'] === UploadBase::OK ) {
809 $this->log->info(
"File verification of {filename} failed for {user} because {result}",
811 'user' => $this->
getUser()->getName(),
812 'resultCode' => $verification[
'status'],
813 'result' => $this->mUpload->getVerificationErrorCode( $verification[
'status'] ),
814 'filename' => $this->mParams[
'filename'] ??
'-',
815 'details' => $verification[
'details'] ??
''
821 $this->checkVerification( $verification );
830 switch ( $verification[
'status'] ) {
832 case UploadBase::MIN_LENGTH_PARTNAME:
833 $this->dieRecoverableError( [
'filename-tooshort' ],
'filename' );
835 case UploadBase::ILLEGAL_FILENAME:
836 $this->dieRecoverableError(
837 [ ApiMessage::create(
838 'illegal-filename',
null, [
'filename' => $verification[
'filtered'] ]
842 case UploadBase::FILENAME_TOO_LONG:
843 $this->dieRecoverableError( [
'filename-toolong' ],
'filename' );
845 case UploadBase::FILETYPE_MISSING:
846 $this->dieRecoverableError( [
'filetype-missing' ],
'filename' );
848 case UploadBase::WINDOWS_NONASCII_FILENAME:
849 $this->dieRecoverableError( [
'windows-nonascii-filename' ],
'filename' );
852 case UploadBase::EMPTY_FILE:
853 $this->dieWithError(
'empty-file' );
855 case UploadBase::FILE_TOO_LARGE:
856 $this->dieWithError(
'file-too-large' );
859 case UploadBase::FILETYPE_BADTYPE:
861 'filetype' => $verification[
'finalExt'],
862 'allowed' => array_values( array_unique(
863 $this->getConfig()->
get( MainConfigNames::FileExtensions ) ) )
866 array_unique( $this->getConfig()->
get( MainConfigNames::FileExtensions ) );
868 'filetype-banned-type',
870 Message::listParam( $extensions,
'comma' ),
871 count( $extensions ),
874 ApiResult::setIndexedTagName( $extradata[
'allowed'],
'ext' );
876 if ( isset( $verification[
'blacklistedExt'] ) ) {
877 $msg[1] = Message::listParam( $verification[
'blacklistedExt'],
'comma' );
878 $msg[4] = count( $verification[
'blacklistedExt'] );
879 $extradata[
'blacklisted'] = array_values( $verification[
'blacklistedExt'] );
880 ApiResult::setIndexedTagName( $extradata[
'blacklisted'],
'ext' );
882 $msg[1] = $verification[
'finalExt'];
886 $this->dieWithError( $msg,
'filetype-banned', $extradata );
889 case UploadBase::VERIFICATION_ERROR:
890 $msg = ApiMessage::create( $verification[
'details'],
'verification-error' );
892 $details = [ $msg->getKey(), ...$msg->getParams() ];
894 $details = $verification[
'details'];
896 ApiResult::setIndexedTagName( $details,
'detail' );
897 $msg->setApiData( $msg->getApiData() + [
'details' => $details ] );
899 $this->dieWithError( $msg );
902 case UploadBase::HOOK_ABORTED:
903 $msg = $verification[
'error'] ===
'' ?
'hookaborted' : $verification[
'error'];
904 $this->dieWithError( $msg,
'hookaborted', [
'details' => $verification[
'error'] ] );
907 $this->dieWithError(
'apierror-unknownerror-nocode',
'unknown-error',
908 [
'details' => [
'code' => $verification[
'status'] ] ] );
920 $warnings = UploadBase::makeWarningsSerializable(
921 $this->mUpload->checkWarnings( $this->getUser() )
924 return $this->transformWarnings( $warnings );
930 ApiResult::setIndexedTagName( $warnings,
'warning' );
932 if ( isset( $warnings[
'duplicate'] ) ) {
933 $dupes = array_column( $warnings[
'duplicate'],
'fileName' );
934 ApiResult::setIndexedTagName( $dupes,
'duplicate' );
935 $warnings[
'duplicate'] = $dupes;
938 if ( isset( $warnings[
'exists'] ) ) {
939 $warning = $warnings[
'exists'];
940 unset( $warnings[
'exists'] );
941 $localFile = $warning[
'normalizedFile'] ?? $warning[
'file'];
942 $warnings[$warning[
'warning']] = $localFile[
'fileName'];
945 if ( isset( $warnings[
'no-change'] ) ) {
946 $file = $warnings[
'no-change'];
947 unset( $warnings[
'no-change'] );
949 $warnings[
'nochange'] = [
950 'timestamp' =>
wfTimestamp( TS_ISO_8601, $file[
'timestamp'] )
954 if ( isset( $warnings[
'duplicate-version'] ) ) {
956 foreach ( $warnings[
'duplicate-version'] as $dupe ) {
958 'timestamp' =>
wfTimestamp( TS_ISO_8601, $dupe[
'timestamp'] )
961 unset( $warnings[
'duplicate-version'] );
963 ApiResult::setIndexedTagName( $dupes,
'ver' );
964 $warnings[
'duplicateversions'] = $dupes;
967 if ( $this->mParams[
'async'] && $this->mParams[
'url'] ) {
968 unset( $warnings[
'empty-file'] );
982 $this->log->info(
"Upload stashing of {filename} failed for {user} because {error}",
984 'user' => $this->
getUser()->getName(),
985 'error' => get_class( $e ),
986 'filename' => $this->mParams[
'filename'] ??
'-',
987 'filekey' => $this->mParams[
'filekey'] ??
'-'
991 switch ( get_class( $e ) ) {
992 case UploadStashFileNotFoundException::class:
993 $wrap =
'apierror-stashedfilenotfound';
995 case UploadStashBadPathException::class:
996 $wrap =
'apierror-stashpathinvalid';
998 case UploadStashFileException::class:
999 $wrap =
'apierror-stashfilestorage';
1001 case UploadStashZeroLengthFileException::class:
1002 $wrap =
'apierror-stashzerolength';
1004 case UploadStashNotLoggedInException::class:
1005 return StatusValue::newFatal( ApiMessage::create(
1006 [
'apierror-mustbeloggedin', $this->msg(
'action-upload' ) ],
'stashnotloggedin'
1008 case UploadStashWrongOwnerException::class:
1009 $wrap =
'apierror-stashwrongowner';
1011 case UploadStashNoSuchKeyException::class:
1012 $wrap =
'apierror-stashnosuchfilekey';
1015 $wrap = [
'uploadstash-exception', get_class( $e ) ];
1018 return StatusValue::newFatal(
1019 $this->getErrorFormatter()->getMessageFromException( $e, [
'wrap' => $wrap ] )
1032 $this->mParams[
'text'] ??= $this->mParams[
'comment'];
1035 $file = $this->mUpload->getLocalFile();
1037 $title = $file->getTitle();
1045 $this->mParams[
'watchlist'], $title, $user,
'watchdefault'
1048 if ( !$watch && $this->mParams[
'watchlist'] ==
'preferences' && !$file->exists() ) {
1057 if ( $this->mParams[
'watch'] ) {
1061 if ( $this->mParams[
'tags'] ) {
1063 if ( !$status->isOK() ) {
1064 $this->dieStatus( $status );
1070 if ( $this->mParams[
'async'] ) {
1072 if ( $this->mParams[
'filekey'] ) {
1075 'filename' => $this->mParams[
'filename'],
1076 'filekey' => $this->mParams[
'filekey'],
1077 'comment' => $this->mParams[
'comment'],
1078 'tags' => $this->mParams[
'tags'] ?? [],
1079 'text' => $this->mParams[
'text'],
1081 'watchlistexpiry' => $watchlistExpiry,
1082 'session' => $this->
getContext()->exportSession(),
1083 'ignorewarnings' => $this->mParams[
'ignorewarnings']
1086 } elseif ( $this->mParams[
'url'] ) {
1089 'filename' => $this->mParams[
'filename'],
1090 'url' => $this->mParams[
'url'],
1091 'comment' => $this->mParams[
'comment'],
1092 'tags' => $this->mParams[
'tags'] ?? [],
1093 'text' => $this->mParams[
'text'],
1095 'watchlistexpiry' => $watchlistExpiry,
1096 'session' => $this->
getContext()->exportSession(),
1097 'ignorewarnings' => $this->mParams[
'ignorewarnings']
1101 $this->dieWithError(
'apierror-no-async-support',
'publishfailed' );
1107 $cacheKey =
$job->getCacheKey();
1110 $progress = UploadBase::getSessionStatus( $this->
getUser(), $cacheKey );
1111 if ( $progress && $progress[
'result'] ===
'Poll' ) {
1112 $this->dieWithError(
'apierror-upload-inprogress',
'publishfailed' );
1114 UploadBase::setSessionStatus(
1117 [
'result' =>
'Poll',
'stage' =>
'queued',
'status' => Status::newGood() ]
1120 $this->jobQueueGroup->push(
$job );
1121 $this->log->info(
"Sending publish job of {filename} for {user}",
1123 'user' => $this->
getUser()->getName(),
1124 'filename' => $this->mParams[
'filename'] ??
'-'
1127 $result[
'result'] =
'Poll';
1128 $result[
'stage'] =
'queued';
1131 $status = $this->mUpload->performUpload(
1132 $this->mParams[
'comment'],
1133 $this->mParams[
'text'],
1136 $this->mParams[
'tags'] ?? [],
1140 if ( !$status->isGood() ) {
1141 $this->log->info(
"Non-async API upload publish failed for {user} because {status}",
1143 'user' => $this->
getUser()->getName(),
1144 'filename' => $this->mParams[
'filename'] ??
'-',
1145 'filekey' => $this->mParams[
'filekey'] ??
'-',
1146 'status' => (
string)$status
1149 $this->dieRecoverableError( $status->getErrors() );
1151 $result[
'result'] =
'Success';
1154 $result[
'filename'] = $file->getName();
1155 if ( $warnings && count( $warnings ) > 0 ) {
1156 $result[
'warnings'] = $warnings;
1173 ParamValidator::PARAM_TYPE =>
'string',
1176 ParamValidator::PARAM_DEFAULT =>
''
1179 ParamValidator::PARAM_TYPE =>
'tags',
1180 ParamValidator::PARAM_ISMULTI =>
true,
1183 ParamValidator::PARAM_TYPE =>
'text',
1186 ParamValidator::PARAM_DEFAULT =>
false,
1187 ParamValidator::PARAM_DEPRECATED =>
true,
1200 'ignorewarnings' =>
false,
1202 ParamValidator::PARAM_TYPE =>
'upload',
1207 ParamValidator::PARAM_DEPRECATED =>
true,
1212 ParamValidator::PARAM_TYPE =>
'integer',
1213 IntegerDef::PARAM_MIN => 0,
1214 IntegerDef::PARAM_MAX => UploadBase::getMaxUploadSize(),
1217 ParamValidator::PARAM_TYPE =>
'integer',
1218 IntegerDef::PARAM_MIN => 0,
1221 ParamValidator::PARAM_TYPE =>
'upload',
1225 'checkstatus' =>
false,
1237 'action=upload&filename=Wiki.png' .
1238 '&url=http%3A//upload.wikimedia.org/wikipedia/en/b/bc/Wiki.png&token=123ABC'
1239 =>
'apihelp-upload-example-url',
1240 'action=upload&filename=Wiki.png&filekey=filekey&ignorewarnings=1&token=123ABC'
1241 =>
'apihelp-upload-example-filekey',
1246 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.
getUpload()
Getter for the upload.
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.
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