37use Psr\Log\LoggerInterface;
55 private LoggerInterface $log;
71 parent::__construct( $mainModule, $moduleName );
72 $this->jobQueueGroup = $jobQueueGroup;
75 $this->watchlistExpiryEnabled = $this->
getConfig()->
get( MainConfigNames::WatchlistExpiry );
76 $this->watchlistMaxDuration =
77 $this->
getConfig()->get( MainConfigNames::WatchlistExpiryMaxDuration );
78 $this->watchlistManager = $watchlistManager;
79 $this->userOptionsLookup = $userOptionsLookup;
80 $this->log = LoggerFactory::getInstance(
'upload' );
85 if ( !UploadBase::isEnabled() ) {
94 $this->mParams[
'async'] = ( $this->mParams[
'async'] &&
95 $this->
getConfig()->get( MainConfigNames::EnableAsyncUploads ) );
98 if ( !$this->mParams[
'filekey'] && $this->mParams[
'sessionkey'] ) {
99 $this->mParams[
'filekey'] = $this->mParams[
'sessionkey'];
102 if ( !$this->mParams[
'checkstatus'] ) {
110 } elseif ( !isset( $this->mUpload ) ) {
111 $this->
dieDebug( __METHOD__,
'No upload module set' );
123 if ( $this->mParams[
'async'] && $this->mParams[
'url'] ) {
124 $status = $this->mUpload->canFetchFile();
126 $status = $this->mUpload->fetchFile();
129 if ( !$status->isGood() ) {
130 $this->log->info(
"Unable to fetch file {filename} for {user} because {status}",
132 'user' => $this->
getUser()->getName(),
133 'status' => (
string)$status,
134 'filename' => $this->mParams[
'filename'] ??
'-',
146 if ( !$this->mParams[
'stash'] ) {
147 $permErrors = $this->mUpload->verifyTitlePermissions( $user );
148 if ( $permErrors !==
true ) {
149 $this->dieRecoverableError( $permErrors,
'filename' );
155 $result = $this->getContextResult();
164 if ( $result[
'result'] ===
'Success' ) {
170 $this->mUpload->cleanupTempFile();
179 $services->getJobQueueGroup(),
180 $services->getWatchlistManager(),
181 $services->getUserOptionsLookup()
202 $result = $this->getResult();
211 array_fill_keys( $imParam,
true ),
219 array_fill_keys( $imParam,
true ),
231 private function getContextResult() {
232 $warnings = $this->getApiWarnings();
233 if ( $warnings && !$this->mParams[
'ignorewarnings'] ) {
235 return $this->getWarningsResult( $warnings );
236 } elseif ( $this->mParams[
'chunk'] ) {
238 return $this->getChunkResult( $warnings );
239 } elseif ( $this->mParams[
'stash'] ) {
241 return $this->getStashResult( $warnings );
246 return $this->performUpload( $warnings );
254 private function getStashResult( $warnings ) {
256 $result[
'result'] =
'Success';
257 if ( $warnings && count( $warnings ) > 0 ) {
258 $result[
'warnings'] = $warnings;
262 $this->performStash(
'critical', $result );
272 private function getWarningsResult( $warnings ) {
274 $result[
'result'] =
'Warning';
275 $result[
'warnings'] = $warnings;
278 $this->performStash(
'optional', $result );
290 $configured = $config->
get( MainConfigNames::MinUploadChunkSize );
295 ini_get(
'post_max_size' ),
304 UploadBase::getMaxUploadSize(
'file' ),
305 UploadBase::getMaxPhpUploadSize(),
315 private function getChunkResult( $warnings ) {
318 if ( $warnings && count( $warnings ) > 0 ) {
319 $result[
'warnings'] = $warnings;
322 $chunkUpload = $this->getMain()->getUpload(
'chunk' );
323 $chunkPath = $chunkUpload->getTempName();
324 $chunkSize = $chunkUpload->getSize();
325 $totalSoFar = $this->mParams[
'offset'] + $chunkSize;
326 $minChunkSize = self::getMinUploadChunkSize( $this->getConfig() );
329 if ( $totalSoFar > $this->mParams[
'filesize'] ) {
330 $this->dieWithError(
'apierror-invalid-chunk' );
334 if ( $totalSoFar != $this->mParams[
'filesize'] && $chunkSize < $minChunkSize ) {
335 $this->dieWithError( [
'apierror-chunk-too-small', Message::numParam( $minChunkSize ) ] );
338 if ( $this->mParams[
'offset'] == 0 ) {
339 $this->log->debug(
"Started first chunk of chunked upload of {filename} for {user}",
341 'user' => $this->
getUser()->getName(),
342 'filename' => $this->mParams[
'filename'] ??
'-',
343 'filesize' => $this->mParams[
'filesize'],
344 'chunkSize' => $chunkSize
347 $filekey = $this->performStash(
'critical' );
349 $filekey = $this->mParams[
'filekey'];
355 $this->log->info(
"Stash failed due to no session for {user}",
357 'user' => $this->
getUser()->getName(),
358 'filename' => $this->mParams[
'filename'] ??
'-',
359 'filekey' => $this->mParams[
'filekey'] ??
'-',
360 'filesize' => $this->mParams[
'filesize'],
361 'chunkSize' => $chunkSize
364 $this->dieWithError(
'apierror-stashfailed-nosession',
'stashfailed' );
365 } elseif ( $progress[
'result'] !==
'Continue' || $progress[
'stage'] !==
'uploading' ) {
366 $this->dieWithError(
'apierror-stashfailed-complete',
'stashfailed' );
369 $status = $this->mUpload->addChunk(
370 $chunkPath, $chunkSize, $this->mParams[
'offset'] );
371 if ( !$status->isGood() ) {
373 'offset' => $this->mUpload->getOffset(),
375 $this->log->info(
"Chunked upload stash failure {status} for {user}",
377 'status' => (
string)$status,
378 'user' => $this->
getUser()->getName(),
379 'filename' => $this->mParams[
'filename'] ??
'-',
380 'filekey' => $this->mParams[
'filekey'] ??
'-',
381 'filesize' => $this->mParams[
'filesize'],
382 'chunkSize' => $chunkSize,
383 'offset' => $this->mUpload->getOffset()
386 $this->dieStatusWithCode( $status,
'stashfailed', $extradata );
388 $this->log->debug(
"Got chunk for {filename} with offset {offset} for {user}",
390 'user' => $this->
getUser()->getName(),
391 'filename' => $this->mParams[
'filename'] ??
'-',
392 'filekey' => $this->mParams[
'filekey'] ??
'-',
393 'filesize' => $this->mParams[
'filesize'],
394 'chunkSize' => $chunkSize,
395 'offset' => $this->mUpload->getOffset()
402 if ( $totalSoFar == $this->mParams[
'filesize'] ) {
403 if ( $this->mParams[
'async'] ) {
407 [
'result' =>
'Poll',
408 'stage' =>
'queued',
'status' => Status::newGood() ]
415 'filename' => $this->mParams[
'filename'],
416 'filekey' => $filekey,
417 'filesize' => $this->mParams[
'filesize'],
418 'session' => $this->
getContext()->exportSession()
420 $this->log->info(
"Received final chunk of {filename} for {user}, queuing assemble job",
422 'user' => $this->
getUser()->getName(),
423 'filename' => $this->mParams[
'filename'] ??
'-',
424 'filekey' => $this->mParams[
'filekey'] ??
'-',
425 'filesize' => $this->mParams[
'filesize'],
426 'chunkSize' => $chunkSize,
429 $result[
'result'] =
'Poll';
430 $result[
'stage'] =
'queued';
432 $this->log->info(
"Received final chunk of {filename} for {user}, assembling immediately",
434 'user' => $this->
getUser()->getName(),
435 'filename' => $this->mParams[
'filename'] ??
'-',
436 'filekey' => $this->mParams[
'filekey'] ??
'-',
437 'filesize' => $this->mParams[
'filesize'],
438 'chunkSize' => $chunkSize,
442 $status = $this->mUpload->concatenateChunks();
443 if ( !$status->isGood() ) {
447 [
'result' =>
'Failure',
'stage' =>
'assembling',
'status' => $status ]
449 $this->log->info(
"Non jobqueue assembly of {filename} failed because {status}",
451 'user' => $this->
getUser()->getName(),
452 'filename' => $this->mParams[
'filename'] ??
'-',
453 'filekey' => $this->mParams[
'filekey'] ??
'-',
454 'filesize' => $this->mParams[
'filesize'],
455 'chunkSize' => $chunkSize,
456 'status' => (
string)$status
459 $this->dieStatusWithCode( $status,
'stashfailed' );
463 $warnings = $this->getApiWarnings();
465 $result[
'warnings'] = $warnings;
471 $this->mUpload->stash->removeFile( $filekey );
472 $filekey = $this->mUpload->getStashFile()->getFileKey();
474 $result[
'result'] =
'Success';
481 'result' =>
'Continue',
482 'stage' =>
'uploading',
483 'offset' => $totalSoFar,
484 'status' => Status::newGood(),
487 $result[
'result'] =
'Continue';
488 $result[
'offset'] = $totalSoFar;
491 $result[
'filekey'] = $filekey;
508 private function performStash( $failureMode, &$data =
null ) {
509 $isPartial = (bool)$this->mParams[
'chunk'];
511 $status = $this->mUpload->tryStashFile( $this->
getUser(), $isPartial );
513 if ( $status->isGood() && !$status->getValue() ) {
515 $status->fatal(
new ApiMessage(
'apierror-stashinvalidfile',
'stashfailed' ) );
517 }
catch ( Exception $e ) {
518 $debugMessage =
'Stashing temporary file failed: ' . get_class( $e ) .
' ' . $e->getMessage();
519 $this->log->info( $debugMessage,
521 'user' => $this->
getUser()->getName(),
522 'filename' => $this->mParams[
'filename'] ??
'-',
523 'filekey' => $this->mParams[
'filekey'] ??
'-'
527 $status = Status::newFatal( $this->getErrorFormatter()->getMessageFromException(
528 $e, [
'wrap' =>
new ApiMessage(
'apierror-stashexception',
'stashfailed' ) ]
532 if ( $status->isGood() ) {
533 $stashFile = $status->getValue();
534 $data[
'filekey'] = $stashFile->getFileKey();
536 $data[
'sessionkey'] = $data[
'filekey'];
537 return $data[
'filekey'];
540 if ( $status->getMessage()->getKey() ===
'uploadstash-exception' ) {
543 [ $exceptionType, $message ] = $status->getMessage()->getParams();
544 $debugMessage =
'Stashing temporary file failed: ' . $exceptionType .
' ' . $message;
545 $this->log->info( $debugMessage,
547 'user' => $this->
getUser()->getName(),
548 'filename' => $this->mParams[
'filename'] ??
'-',
549 'filekey' => $this->mParams[
'filekey'] ??
'-'
554 $this->log->info(
"Stash upload failure {status}",
556 'status' => (
string)$status,
557 'user' => $this->
getUser()->getName(),
558 'filename' => $this->mParams[
'filename'] ??
'-',
559 'filekey' => $this->mParams[
'filekey'] ??
'-'
563 if ( $failureMode !==
'optional' ) {
564 $this->dieStatus( $status );
566 $data[
'stasherrors'] = $this->getErrorFormatter()->arrayFromStatus( $status );
581 private function dieRecoverableError( $errors, $parameter =
null ) {
582 $this->performStash(
'optional', $data );
585 $data[
'invalidparameter'] = $parameter;
589 foreach ( $errors as $error ) {
591 $msg->setApiData( $msg->getApiData() + $data );
594 $this->dieStatus( $sv );
608 $sv = StatusValue::newGood();
609 foreach ( $status->getMessages() as $error ) {
610 $msg = ApiMessage::create( $error, $overrideCode );
611 if ( $moreExtraData ) {
612 $msg->setApiData( $msg->getApiData() + $moreExtraData );
616 $this->dieStatus( $sv );
628 if ( !$this->mParams[
'chunk'] ) {
629 $this->requireOnlyOneParameter( $this->mParams,
630 'filekey',
'file',
'url' );
634 if ( $this->mParams[
'checkstatus'] && ( $this->mParams[
'filekey'] || $this->mParams[
'url'] ) ) {
636 $progress = UploadBase::getSessionStatus( $this->
getUser(), $statusKey );
638 $this->log->info(
"Cannot check upload status due to missing upload session for {user}",
640 'user' => $this->
getUser()->getName(),
641 'filename' => $this->mParams[
'filename'] ??
'-',
642 'filekey' => $this->mParams[
'filekey'] ??
'-'
645 $this->dieWithError(
'apierror-upload-missingresult',
'missingresult' );
646 } elseif ( !$progress[
'status']->isGood() ) {
647 $this->dieStatusWithCode( $progress[
'status'],
'stashfailed' );
649 if ( isset( $progress[
'status']->value[
'verification'] ) ) {
650 $this->checkVerification( $progress[
'status']->value[
'verification'] );
652 if ( isset( $progress[
'status']->value[
'warnings'] ) ) {
653 $warnings = $this->transformWarnings( $progress[
'status']->value[
'warnings'] );
655 $progress[
'warnings'] = $warnings;
658 unset( $progress[
'status'] );
660 if ( isset( $progress[
'imageinfo'] ) ) {
661 $imageinfo = $progress[
'imageinfo'];
662 unset( $progress[
'imageinfo'] );
665 $this->getResult()->addValue(
null, $this->getModuleName(), $progress );
669 $this->getResult()->addValue( $this->getModuleName(),
'imageinfo', $imageinfo );
676 if ( $this->mParams[
'filename'] ===
null ) {
677 $this->dieWithError( [
'apierror-missingparam',
'filename' ] );
680 if ( $this->mParams[
'chunk'] ) {
683 if ( isset( $this->mParams[
'filekey'] ) ) {
684 if ( $this->mParams[
'offset'] === 0 ) {
685 $this->dieWithError(
'apierror-upload-filekeynotallowed',
'filekeynotallowed' );
689 $this->mUpload->continueChunks(
690 $this->mParams[
'filename'],
691 $this->mParams[
'filekey'],
695 if ( $this->mParams[
'offset'] !== 0 ) {
696 $this->dieWithError(
'apierror-upload-filekeyneeded',
'filekeyneeded' );
700 $this->mUpload->initialize(
701 $this->mParams[
'filename'],
705 } elseif ( isset( $this->mParams[
'filekey'] ) ) {
707 if ( !UploadFromStash::isValidKey( $this->mParams[
'filekey'] ) ) {
708 $this->dieWithError(
'apierror-invalid-file-key' );
714 $this->mUpload->initialize(
715 $this->mParams[
'filekey'], $this->mParams[
'filename'], !$this->mParams[
'async']
717 } elseif ( isset( $this->mParams[
'file'] ) ) {
721 if ( $this->mParams[
'async'] ) {
722 $this->dieWithError(
'apierror-cannot-async-upload-file' );
726 $this->mUpload->initialize(
727 $this->mParams[
'filename'],
730 } elseif ( isset( $this->mParams[
'url'] ) ) {
733 $this->dieWithError(
'copyuploaddisabled' );
737 $this->dieWithError(
'apierror-copyuploadbaddomain' );
741 $this->dieWithError(
'apierror-copyuploadbadurl' );
745 $this->mUpload->
initialize( $this->mParams[
'filename'],
746 $this->mParams[
'url'] );
759 $permission = $this->mUpload->isAllowed( $user );
761 if ( $permission !==
true ) {
762 if ( !$user->isNamed() ) {
763 $this->dieWithError( [
'apierror-mustbeloggedin', $this->msg(
'action-upload' ) ] );
766 $this->dieStatus( User::newFatalPermissionDeniedStatus( $permission ) );
770 if ( $user->isBlockedFromUpload() ) {
772 $this->dieBlocked( $user->getBlock() );
780 if ( $this->mParams[
'chunk'] ) {
781 $maxSize = UploadBase::getMaxUploadSize();
782 if ( $this->mParams[
'filesize'] > $maxSize ) {
783 $this->dieWithError(
'file-too-large' );
785 if ( !$this->mUpload->getTitle() ) {
786 $this->dieWithError(
'illegal-filename' );
790 $verification = $this->mUpload->validateName();
791 if ( $verification ===
true ) {
794 } elseif ( $this->mParams[
'async'] && ( $this->mParams[
'filekey'] || $this->mParams[
'url'] ) ) {
798 $verification = $this->mUpload->validateName();
799 if ( $verification ===
true ) {
803 wfDebug( __METHOD__ .
" about to verify" );
805 $verification = $this->mUpload->verifyUpload();
807 if ( $verification[
'status'] === UploadBase::OK ) {
810 $this->log->info(
"File verification of {filename} failed for {user} because {result}",
812 'user' => $this->
getUser()->getName(),
813 'resultCode' => $verification[
'status'],
814 'result' => $this->mUpload->getVerificationErrorCode( $verification[
'status'] ),
815 'filename' => $this->mParams[
'filename'] ??
'-',
816 'details' => $verification[
'details'] ??
''
822 $this->checkVerification( $verification );
831 switch ( $verification[
'status'] ) {
833 case UploadBase::MIN_LENGTH_PARTNAME:
834 $this->dieRecoverableError( [
'filename-tooshort' ],
'filename' );
836 case UploadBase::ILLEGAL_FILENAME:
837 $this->dieRecoverableError(
838 [ ApiMessage::create(
839 'illegal-filename',
null, [
'filename' => $verification[
'filtered'] ]
843 case UploadBase::FILENAME_TOO_LONG:
844 $this->dieRecoverableError( [
'filename-toolong' ],
'filename' );
846 case UploadBase::FILETYPE_MISSING:
847 $this->dieRecoverableError( [
'filetype-missing' ],
'filename' );
849 case UploadBase::WINDOWS_NONASCII_FILENAME:
850 $this->dieRecoverableError( [
'windows-nonascii-filename' ],
'filename' );
853 case UploadBase::EMPTY_FILE:
854 $this->dieWithError(
'empty-file' );
856 case UploadBase::FILE_TOO_LARGE:
857 $this->dieWithError(
'file-too-large' );
860 case UploadBase::FILETYPE_BADTYPE:
862 'filetype' => $verification[
'finalExt'],
863 'allowed' => array_values( array_unique(
864 $this->getConfig()->
get( MainConfigNames::FileExtensions ) ) )
867 array_unique( $this->getConfig()->
get( MainConfigNames::FileExtensions ) );
869 'filetype-banned-type',
871 Message::listParam( $extensions,
'comma' ),
872 count( $extensions ),
875 ApiResult::setIndexedTagName( $extradata[
'allowed'],
'ext' );
877 if ( isset( $verification[
'blacklistedExt'] ) ) {
878 $msg[1] = Message::listParam( $verification[
'blacklistedExt'],
'comma' );
879 $msg[4] = count( $verification[
'blacklistedExt'] );
880 $extradata[
'blacklisted'] = array_values( $verification[
'blacklistedExt'] );
881 ApiResult::setIndexedTagName( $extradata[
'blacklisted'],
'ext' );
883 $msg[1] = $verification[
'finalExt'];
887 $this->dieWithError( $msg,
'filetype-banned', $extradata );
890 case UploadBase::VERIFICATION_ERROR:
891 $msg = ApiMessage::create( $verification[
'details'],
'verification-error' );
893 $details = [ $msg->getKey(), ...$msg->getParams() ];
895 $details = $verification[
'details'];
897 ApiResult::setIndexedTagName( $details,
'detail' );
898 $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->getMessages() );
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';
getExpiryFromParams(array $params)
Get formatted expiry from the given parameters, or null if no expiry was provided.
getWatchlistValue(string $watchlist, PageIdentity $page, User $user, ?string $userOption=null)
Return true if we're to watch the page, false if not.
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