MediaWiki  1.23.14
ApiUpload.php
Go to the documentation of this file.
1 <?php
30 class ApiUpload extends ApiBase {
32  protected $mUpload = null;
33 
34  protected $mParams;
35 
36  public function execute() {
37  global $wgEnableAsyncUploads;
38 
39  // Check whether upload is enabled
40  if ( !UploadBase::isEnabled() ) {
41  $this->dieUsageMsg( 'uploaddisabled' );
42  }
43 
44  $user = $this->getUser();
45 
46  // Parameter handling
47  $this->mParams = $this->extractRequestParams();
48  $request = $this->getMain()->getRequest();
49  // Check if async mode is actually supported (jobs done in cli mode)
50  $this->mParams['async'] = ( $this->mParams['async'] && $wgEnableAsyncUploads );
51  // Add the uploaded file to the params array
52  $this->mParams['file'] = $request->getFileName( 'file' );
53  $this->mParams['chunk'] = $request->getFileName( 'chunk' );
54 
55  // Copy the session key to the file key, for backward compatibility.
56  if ( !$this->mParams['filekey'] && $this->mParams['sessionkey'] ) {
57  $this->mParams['filekey'] = $this->mParams['sessionkey'];
58  }
59 
60  // Select an upload module
61  try {
62  if ( !$this->selectUploadModule() ) {
63  return; // not a true upload, but a status request or similar
64  } elseif ( !isset( $this->mUpload ) ) {
65  $this->dieUsage( 'No upload module set', 'nomodule' );
66  }
67  } catch ( UploadStashException $e ) { // XXX: don't spam exception log
68  $this->dieUsage( get_class( $e ) . ": " . $e->getMessage(), 'stasherror' );
69  }
70 
71  // First check permission to upload
72  $this->checkPermissions( $user );
73 
74  // Fetch the file (usually a no-op)
76  $status = $this->mUpload->fetchFile();
77  if ( !$status->isGood() ) {
78  $errors = $status->getErrorsArray();
79  $error = array_shift( $errors[0] );
80  $this->dieUsage( 'Error fetching file from remote source', $error, 0, $errors[0] );
81  }
82 
83  // Check if the uploaded file is sane
84  if ( $this->mParams['chunk'] ) {
85  $maxSize = UploadBase::getMaxUploadSize();
86  if ( $this->mParams['filesize'] > $maxSize ) {
87  $this->dieUsage( 'The file you submitted was too large', 'file-too-large' );
88  }
89  if ( !$this->mUpload->getTitle() ) {
90  $this->dieUsage( 'Invalid file title supplied', 'internal-error' );
91  }
92  } elseif ( $this->mParams['async'] && $this->mParams['filekey'] ) {
93  // defer verification to background process
94  } else {
95  wfDebug( __METHOD__ . " about to verify\n" );
96  $this->verifyUpload();
97  }
98 
99  // Check if the user has the rights to modify or overwrite the requested title
100  // (This check is irrelevant if stashing is already requested, since the errors
101  // can always be fixed by changing the title)
102  if ( !$this->mParams['stash'] ) {
103  $permErrors = $this->mUpload->verifyTitlePermissions( $user );
104  if ( $permErrors !== true ) {
105  $this->dieRecoverableError( $permErrors[0], 'filename' );
106  }
107  }
108 
109  // Get the result based on the current upload context:
110  try {
111  $result = $this->getContextResult();
112  if ( $result['result'] === 'Success' ) {
113  $result['imageinfo'] = $this->mUpload->getImageInfo( $this->getResult() );
114  }
115  } catch ( UploadStashException $e ) { // XXX: don't spam exception log
116  $this->dieUsage( get_class( $e ) . ": " . $e->getMessage(), 'stasherror' );
117  }
118 
119  $this->getResult()->addValue( null, $this->getModuleName(), $result );
120 
121  // Cleanup any temporary mess
122  $this->mUpload->cleanupTempFile();
123  }
124 
129  private function getContextResult() {
130  $warnings = $this->getApiWarnings();
131  if ( $warnings && !$this->mParams['ignorewarnings'] ) {
132  // Get warnings formatted in result array format
133  return $this->getWarningsResult( $warnings );
134  } elseif ( $this->mParams['chunk'] ) {
135  // Add chunk, and get result
136  return $this->getChunkResult( $warnings );
137  } elseif ( $this->mParams['stash'] ) {
138  // Stash the file and get stash result
139  return $this->getStashResult( $warnings );
140  }
141 
142  // Check throttle after we've handled warnings
143  if ( UploadBase::isThrottled( $this->getUser() )
144  ) {
145  $this->dieUsageMsg( 'actionthrottledtext' );
146  }
147 
148  // This is the most common case -- a normal upload with no warnings
149  // performUpload will return a formatted properly for the API with status
150  return $this->performUpload( $warnings );
151  }
152 
158  private function getStashResult( $warnings ) {
159  $result = array();
160  // Some uploads can request they be stashed, so as not to publish them immediately.
161  // In this case, a failure to stash ought to be fatal
162  try {
163  $result['result'] = 'Success';
164  $result['filekey'] = $this->performStash();
165  $result['sessionkey'] = $result['filekey']; // backwards compatibility
166  if ( $warnings && count( $warnings ) > 0 ) {
167  $result['warnings'] = $warnings;
168  }
169  } catch ( MWException $e ) {
170  $this->dieUsage( $e->getMessage(), 'stashfailed' );
171  }
172 
173  return $result;
174  }
175 
181  private function getWarningsResult( $warnings ) {
182  $result = array();
183  $result['result'] = 'Warning';
184  $result['warnings'] = $warnings;
185  // in case the warnings can be fixed with some further user action, let's stash this upload
186  // and return a key they can use to restart it
187  try {
188  $result['filekey'] = $this->performStash();
189  $result['sessionkey'] = $result['filekey']; // backwards compatibility
190  } catch ( MWException $e ) {
191  $result['warnings']['stashfailed'] = $e->getMessage();
192  }
193 
194  return $result;
195  }
196 
202  private function getChunkResult( $warnings ) {
203  $result = array();
204 
205  if ( $warnings && count( $warnings ) > 0 ) {
206  $result['warnings'] = $warnings;
207  }
208 
209  $request = $this->getMain()->getRequest();
210  $chunkPath = $request->getFileTempname( 'chunk' );
211  $chunkSize = $request->getUpload( 'chunk' )->getSize();
212  $totalSoFar = $this->mParams['offset'] + $chunkSize;
213  $minChunkSize = $this->getConfig()->get( 'MinUploadChunkSize' );
214 
215  // Sanity check sizing
216  if ( $totalSoFar > $this->mParams['filesize'] ) {
217  $this->dieUsage(
218  'Offset plus current chunk is greater than claimed file size', 'invalid-chunk'
219  );
220  }
221 
222  // Enforce minimum chunk size
223  if ( $totalSoFar != $this->mParams['filesize'] && $chunkSize < $minChunkSize ) {
224  $this->dieUsage(
225  "Minimum chunk size is $minChunkSize bytes for non-final chunks", 'chunk-too-small'
226  );
227  }
228 
229  if ( $this->mParams['offset'] == 0 ) {
230  try {
231  $filekey = $this->performStash();
232  } catch ( MWException $e ) {
233  // FIXME: Error handling here is wrong/different from rest of this
234  $this->dieUsage( $e->getMessage(), 'stashfailed' );
235  }
236  } else {
237  $filekey = $this->mParams['filekey'];
238 
239  // Don't allow further uploads to an already-completed session
240  $progress = UploadBase::getSessionStatus( $this->getUser(), $filekey );
241  if ( !$progress ) {
242  // Probably can't get here, but check anyway just in case
243  $this->dieUsage( 'No chunked upload session with this key', 'stashfailed' );
244  } elseif ( $progress['result'] !== 'Continue' || $progress['stage'] !== 'uploading' ) {
245  $this->dieUsage(
246  'Chunked upload is already completed, check status for details', 'stashfailed'
247  );
248  }
249 
251  $status = $this->mUpload->addChunk(
252  $chunkPath, $chunkSize, $this->mParams['offset'] );
253  if ( !$status->isGood() ) {
254  $this->dieUsage( $status->getWikiText(), 'stashfailed' );
255  }
256  }
257 
258  // Check we added the last chunk:
259  if ( $totalSoFar == $this->mParams['filesize'] ) {
260  if ( $this->mParams['async'] ) {
261  UploadBase::setSessionStatus(
262  $filekey,
263  array( 'result' => 'Poll',
264  'stage' => 'queued', 'status' => Status::newGood() )
265  );
267  Title::makeTitle( NS_FILE, $filekey ),
268  array(
269  'filename' => $this->mParams['filename'],
270  'filekey' => $filekey,
271  'session' => $this->getContext()->exportSession()
272  )
273  ) );
274  if ( $ok ) {
275  $result['result'] = 'Poll';
276  } else {
277  UploadBase::setSessionStatus( $filekey, false );
278  $this->dieUsage(
279  "Failed to start AssembleUploadChunks.php", 'stashfailed' );
280  }
281  } else {
282  $status = $this->mUpload->concatenateChunks();
283  if ( !$status->isGood() ) {
284  UploadBase::setSessionStatus(
285  $this->getUser(),
286  $filekey,
287  array( 'result' => 'Failure', 'stage' => 'assembling', 'status' => $status )
288  );
289  $this->dieUsage( $status->getWikiText(), 'stashfailed' );
290  }
291 
292  // The fully concatenated file has a new filekey. So remove
293  // the old filekey and fetch the new one.
294  UploadBase::setSessionStatus( $this->getUser(), $filekey, false );
295  $this->mUpload->stash->removeFile( $filekey );
296  $filekey = $this->mUpload->getLocalFile()->getFileKey();
297 
298  $result['result'] = 'Success';
299  }
300  } else {
301  UploadBase::setSessionStatus(
302  $this->getUser(),
303  $filekey,
304  array(
305  'result' => 'Continue',
306  'stage' => 'uploading',
307  'offset' => $totalSoFar,
308  'status' => Status::newGood(),
309  )
310  );
311  $result['result'] = 'Continue';
312  $result['offset'] = $totalSoFar;
313  }
314  $result['filekey'] = $filekey;
315 
316  return $result;
317  }
318 
325  private function performStash() {
326  try {
327  $stashFile = $this->mUpload->stashFile();
328 
329  if ( !$stashFile ) {
330  throw new MWException( 'Invalid stashed file' );
331  }
332  $fileKey = $stashFile->getFileKey();
333  } catch ( MWException $e ) {
334  $message = 'Stashing temporary file failed: ' . get_class( $e ) . ' ' . $e->getMessage();
335  wfDebug( __METHOD__ . ' ' . $message . "\n" );
336  throw new MWException( $message );
337  }
338 
339  return $fileKey;
340  }
341 
351  private function dieRecoverableError( $error, $parameter, $data = array() ) {
352  try {
353  $data['filekey'] = $this->performStash();
354  $data['sessionkey'] = $data['filekey'];
355  } catch ( MWException $e ) {
356  $data['stashfailed'] = $e->getMessage();
357  }
358  $data['invalidparameter'] = $parameter;
359 
360  $parsed = $this->parseMsg( $error );
361  $this->dieUsage( $parsed['info'], $parsed['code'], 0, $data );
362  }
363 
371  protected function selectUploadModule() {
372  $request = $this->getMain()->getRequest();
373 
374  // chunk or one and only one of the following parameters is needed
375  if ( !$this->mParams['chunk'] ) {
376  $this->requireOnlyOneParameter( $this->mParams,
377  'filekey', 'file', 'url', 'statuskey' );
378  }
379 
380  // Status report for "upload to stash"/"upload from stash"
381  if ( $this->mParams['filekey'] && $this->mParams['checkstatus'] ) {
382  $progress = UploadBase::getSessionStatus( $this->mParams['filekey'] );
383  if ( !$progress ) {
384  $this->dieUsage( 'No result in status data', 'missingresult' );
385  } elseif ( !$progress['status']->isGood() ) {
386  $this->dieUsage( $progress['status']->getWikiText(), 'stashfailed' );
387  }
388  if ( isset( $progress['status']->value['verification'] ) ) {
389  $this->checkVerification( $progress['status']->value['verification'] );
390  }
391  unset( $progress['status'] ); // remove Status object
392  $this->getResult()->addValue( null, $this->getModuleName(), $progress );
393 
394  return false;
395  }
396 
397  if ( $this->mParams['statuskey'] ) {
398  $this->checkAsyncDownloadEnabled();
399 
400  // Status request for an async upload
401  $sessionData = UploadFromUrlJob::getSessionData( $this->mParams['statuskey'] );
402  if ( !isset( $sessionData['result'] ) ) {
403  $this->dieUsage( 'No result in session data', 'missingresult' );
404  }
405  if ( $sessionData['result'] == 'Warning' ) {
406  $sessionData['warnings'] = $this->transformWarnings( $sessionData['warnings'] );
407  $sessionData['sessionkey'] = $this->mParams['statuskey'];
408  }
409  $this->getResult()->addValue( null, $this->getModuleName(), $sessionData );
410 
411  return false;
412  }
413 
414  // The following modules all require the filename parameter to be set
415  if ( is_null( $this->mParams['filename'] ) ) {
416  $this->dieUsageMsg( array( 'missingparam', 'filename' ) );
417  }
418 
419  if ( $this->mParams['chunk'] ) {
420  // Chunk upload
421  $this->mUpload = new UploadFromChunks();
422  if ( isset( $this->mParams['filekey'] ) ) {
423  if ( $this->mParams['offset'] === 0 ) {
424  $this->dieUsage( 'Cannot supply a filekey when offset is 0', 'badparams' );
425  }
426 
427  // handle new chunk
428  $this->mUpload->continueChunks(
429  $this->mParams['filename'],
430  $this->mParams['filekey'],
431  $request->getUpload( 'chunk' )
432  );
433  } else {
434  // handle first chunk
435  $this->mUpload->initialize(
436  $this->mParams['filename'],
437  $request->getUpload( 'chunk' )
438  );
439  }
440  } elseif ( isset( $this->mParams['filekey'] ) ) {
441  // Upload stashed in a previous request
442  if ( !UploadFromStash::isValidKey( $this->mParams['filekey'] ) ) {
443  $this->dieUsageMsg( 'invalid-file-key' );
444  }
445 
446  $this->mUpload = new UploadFromStash( $this->getUser() );
447  // This will not download the temp file in initialize() in async mode.
448  // We still have enough information to call checkWarnings() and such.
449  $this->mUpload->initialize(
450  $this->mParams['filekey'], $this->mParams['filename'], !$this->mParams['async']
451  );
452  } elseif ( isset( $this->mParams['file'] ) ) {
453  $this->mUpload = new UploadFromFile();
454  $this->mUpload->initialize(
455  $this->mParams['filename'],
456  $request->getUpload( 'file' )
457  );
458  } elseif ( isset( $this->mParams['url'] ) ) {
459  // Make sure upload by URL is enabled:
460  if ( !UploadFromUrl::isEnabled() ) {
461  $this->dieUsageMsg( 'copyuploaddisabled' );
462  }
463 
464  if ( !UploadFromUrl::isAllowedHost( $this->mParams['url'] ) ) {
465  $this->dieUsageMsg( 'copyuploadbaddomain' );
466  }
467 
468  if ( !UploadFromUrl::isAllowedUrl( $this->mParams['url'] ) ) {
469  $this->dieUsageMsg( 'copyuploadbadurl' );
470  }
471 
472  $async = false;
473  if ( $this->mParams['asyncdownload'] ) {
474  $this->checkAsyncDownloadEnabled();
475 
476  if ( $this->mParams['leavemessage'] && !$this->mParams['ignorewarnings'] ) {
477  $this->dieUsage( 'Using leavemessage without ignorewarnings is not supported',
478  'missing-ignorewarnings' );
479  }
480 
481  if ( $this->mParams['leavemessage'] ) {
482  $async = 'async-leavemessage';
483  } else {
484  $async = 'async';
485  }
486  }
487  $this->mUpload = new UploadFromUrl;
488  $this->mUpload->initialize( $this->mParams['filename'],
489  $this->mParams['url'], $async );
490  }
491 
492  return true;
493  }
494 
500  protected function checkPermissions( $user ) {
501  // Check whether the user has the appropriate permissions to upload anyway
502  $permission = $this->mUpload->isAllowed( $user );
503 
504  if ( $permission !== true ) {
505  if ( !$user->isLoggedIn() ) {
506  $this->dieUsageMsg( array( 'mustbeloggedin', 'upload' ) );
507  }
508 
509  $this->dieUsageMsg( 'badaccess-groups' );
510  }
511  }
512 
516  protected function verifyUpload() {
517  $verification = $this->mUpload->verifyUpload();
518  if ( $verification['status'] === UploadBase::OK ) {
519  return;
520  }
521 
522  $this->checkVerification( $verification );
523  }
524 
528  protected function checkVerification( array $verification ) {
530 
531  // @todo Move them to ApiBase's message map
532  switch ( $verification['status'] ) {
533  // Recoverable errors
534  case UploadBase::MIN_LENGTH_PARTNAME:
535  $this->dieRecoverableError( 'filename-tooshort', 'filename' );
536  break;
537  case UploadBase::ILLEGAL_FILENAME:
538  $this->dieRecoverableError( 'illegal-filename', 'filename',
539  array( 'filename' => $verification['filtered'] ) );
540  break;
541  case UploadBase::FILENAME_TOO_LONG:
542  $this->dieRecoverableError( 'filename-toolong', 'filename' );
543  break;
544  case UploadBase::FILETYPE_MISSING:
545  $this->dieRecoverableError( 'filetype-missing', 'filename' );
546  break;
547  case UploadBase::WINDOWS_NONASCII_FILENAME:
548  $this->dieRecoverableError( 'windows-nonascii-filename', 'filename' );
549  break;
550 
551  // Unrecoverable errors
552  case UploadBase::EMPTY_FILE:
553  $this->dieUsage( 'The file you submitted was empty', 'empty-file' );
554  break;
555  case UploadBase::FILE_TOO_LARGE:
556  $this->dieUsage( 'The file you submitted was too large', 'file-too-large' );
557  break;
558 
559  case UploadBase::FILETYPE_BADTYPE:
560  $extradata = array(
561  'filetype' => $verification['finalExt'],
562  'allowed' => array_values( array_unique( $wgFileExtensions ) )
563  );
564  $this->getResult()->setIndexedTagName( $extradata['allowed'], 'ext' );
565 
566  $msg = "Filetype not permitted: ";
567  if ( isset( $verification['blacklistedExt'] ) ) {
568  $msg .= join( ', ', $verification['blacklistedExt'] );
569  $extradata['blacklisted'] = array_values( $verification['blacklistedExt'] );
570  $this->getResult()->setIndexedTagName( $extradata['blacklisted'], 'ext' );
571  } else {
572  $msg .= $verification['finalExt'];
573  }
574  $this->dieUsage( $msg, 'filetype-banned', 0, $extradata );
575  break;
576  case UploadBase::VERIFICATION_ERROR:
577  $this->getResult()->setIndexedTagName( $verification['details'], 'detail' );
578  $this->dieUsage( 'This file did not pass file verification', 'verification-error',
579  0, array( 'details' => $verification['details'] ) );
580  break;
581  case UploadBase::HOOK_ABORTED:
582  $this->dieUsage( "The modification you tried to make was aborted by an extension hook",
583  'hookaborted', 0, array( 'error' => $verification['error'] ) );
584  break;
585  default:
586  $this->dieUsage( 'An unknown error occurred', 'unknown-error',
587  0, array( 'code' => $verification['status'] ) );
588  break;
589  }
590  }
591 
599  protected function getApiWarnings() {
600  $warnings = $this->mUpload->checkWarnings();
601 
602  return $this->transformWarnings( $warnings );
603  }
604 
605  protected function transformWarnings( $warnings ) {
606  if ( $warnings ) {
607  // Add indices
608  $result = $this->getResult();
609  $result->setIndexedTagName( $warnings, 'warning' );
610 
611  if ( isset( $warnings['duplicate'] ) ) {
612  $dupes = array();
613  foreach ( $warnings['duplicate'] as $dupe ) {
614  $dupes[] = $dupe->getName();
615  }
616  $result->setIndexedTagName( $dupes, 'duplicate' );
617  $warnings['duplicate'] = $dupes;
618  }
619 
620  if ( isset( $warnings['exists'] ) ) {
621  $warning = $warnings['exists'];
622  unset( $warnings['exists'] );
623  $localFile = isset( $warning['normalizedFile'] )
624  ? $warning['normalizedFile']
625  : $warning['file'];
626  $warnings[$warning['warning']] = $localFile->getName();
627  }
628  }
629 
630  return $warnings;
631  }
632 
640  protected function performUpload( $warnings ) {
641  // Use comment as initial page text by default
642  if ( is_null( $this->mParams['text'] ) ) {
643  $this->mParams['text'] = $this->mParams['comment'];
644  }
645 
647  $file = $this->mUpload->getLocalFile();
648 
649  // For preferences mode, we want to watch if 'watchdefault' is set or
650  // if the *file* doesn't exist and 'watchcreations' is set. But
651  // getWatchlistValue()'s automatic handling checks if the *title*
652  // exists or not, so we need to check both prefs manually.
653  $watch = $this->getWatchlistValue(
654  $this->mParams['watchlist'], $file->getTitle(), 'watchdefault'
655  );
656  if ( !$watch && $this->mParams['watchlist'] == 'preferences' && !$file->exists() ) {
657  $watch = $this->getWatchlistValue(
658  $this->mParams['watchlist'], $file->getTitle(), 'watchcreations'
659  );
660  }
661 
662  // Deprecated parameters
663  if ( $this->mParams['watch'] ) {
664  $watch = true;
665  }
666 
667  // No errors, no warnings: do the upload
668  if ( $this->mParams['async'] ) {
669  $progress = UploadBase::getSessionStatus( $this->mParams['filekey'] );
670  if ( $progress && $progress['result'] === 'Poll' ) {
671  $this->dieUsage( "Upload from stash already in progress.", 'publishfailed' );
672  }
673  UploadBase::setSessionStatus(
674  $this->mParams['filekey'],
675  array( 'result' => 'Poll', 'stage' => 'queued', 'status' => Status::newGood() )
676  );
678  Title::makeTitle( NS_FILE, $this->mParams['filename'] ),
679  array(
680  'filename' => $this->mParams['filename'],
681  'filekey' => $this->mParams['filekey'],
682  'comment' => $this->mParams['comment'],
683  'text' => $this->mParams['text'],
684  'watch' => $watch,
685  'session' => $this->getContext()->exportSession()
686  )
687  ) );
688  if ( $ok ) {
689  $result['result'] = 'Poll';
690  } else {
691  UploadBase::setSessionStatus( $this->mParams['filekey'], false );
692  $this->dieUsage(
693  "Failed to start PublishStashedFile.php", 'publishfailed' );
694  }
695  } else {
697  $status = $this->mUpload->performUpload( $this->mParams['comment'],
698  $this->mParams['text'], $watch, $this->getUser() );
699 
700  if ( !$status->isGood() ) {
701  $error = $status->getErrorsArray();
702 
703  if ( count( $error ) == 1 && $error[0][0] == 'async' ) {
704  // The upload can not be performed right now, because the user
705  // requested so
706  return array(
707  'result' => 'Queued',
708  'statuskey' => $error[0][1],
709  );
710  }
711 
712  $this->getResult()->setIndexedTagName( $error, 'error' );
713  $this->dieUsage( 'An internal error occurred', 'internal-error', 0, $error );
714  }
715  $result['result'] = 'Success';
716  }
717 
718  $result['filename'] = $file->getName();
719  if ( $warnings && count( $warnings ) > 0 ) {
720  $result['warnings'] = $warnings;
721  }
722 
723  return $result;
724  }
725 
729  protected function checkAsyncDownloadEnabled() {
730  global $wgAllowAsyncCopyUploads;
731  if ( !$wgAllowAsyncCopyUploads ) {
732  $this->dieUsage( 'Asynchronous copy uploads disabled', 'asynccopyuploaddisabled' );
733  }
734  }
735 
736  public function mustBePosted() {
737  return true;
738  }
739 
740  public function isWriteMode() {
741  return true;
742  }
743 
744  public function getAllowedParams() {
745  $params = array(
746  'filename' => array(
747  ApiBase::PARAM_TYPE => 'string',
748  ),
749  'comment' => array(
750  ApiBase::PARAM_DFLT => ''
751  ),
752  'text' => null,
753  'token' => array(
754  ApiBase::PARAM_TYPE => 'string',
756  ),
757  'watch' => array(
758  ApiBase::PARAM_DFLT => false,
760  ),
761  'watchlist' => array(
762  ApiBase::PARAM_DFLT => 'preferences',
764  'watch',
765  'preferences',
766  'nochange'
767  ),
768  ),
769  'ignorewarnings' => false,
770  'file' => array(
771  ApiBase::PARAM_TYPE => 'upload',
772  ),
773  'url' => null,
774  'filekey' => null,
775  'sessionkey' => array(
776  ApiBase::PARAM_DFLT => null,
778  ),
779  'stash' => false,
780 
781  'filesize' => array(
782  ApiBase::PARAM_TYPE => 'integer',
783  ApiBase::PARAM_MIN => 0,
784  ApiBase::PARAM_MAX => UploadBase::getMaxUploadSize(),
785  ),
786  'offset' => array(
787  ApiBase::PARAM_TYPE => 'integer',
788  ApiBase::PARAM_MIN => 0,
789  ),
790  'chunk' => array(
791  ApiBase::PARAM_TYPE => 'upload',
792  ),
793 
794  'async' => false,
795  'asyncdownload' => false,
796  'leavemessage' => false,
797  'statuskey' => null,
798  'checkstatus' => false,
799  );
800 
801  return $params;
802  }
803 
804  public function getParamDescription() {
805  $params = array(
806  'filename' => 'Target filename',
807  'token' => 'Edit token. You can get one of these through prop=info',
808  'comment' => 'Upload comment. Also used as the initial page text for new ' .
809  'files if "text" is not specified',
810  'text' => 'Initial page text for new files',
811  'watch' => 'Watch the page',
812  'watchlist' => 'Unconditionally add or remove the page from your watchlist, ' .
813  'use preferences or do not change watch',
814  'ignorewarnings' => 'Ignore any warnings',
815  'file' => 'File contents',
816  'url' => 'URL to fetch the file from',
817  'filekey' => 'Key that identifies a previous upload that was stashed temporarily.',
818  'sessionkey' => 'Same as filekey, maintained for backward compatibility.',
819  'stash' => 'If set, the server will not add the file to the repository ' .
820  'and stash it temporarily.',
821 
822  'chunk' => 'Chunk contents',
823  'offset' => 'Offset of chunk in bytes',
824  'filesize' => 'Filesize of entire upload',
825 
826  'async' => 'Make potentially large file operations asynchronous when possible',
827  'asyncdownload' => 'Make fetching a URL asynchronous',
828  'leavemessage' => 'If asyncdownload is used, leave a message on the user talk page if finished',
829  'statuskey' => 'Fetch the upload status for this file key (upload by URL)',
830  'checkstatus' => 'Only fetch the upload status for the given file key',
831  );
832 
833  return $params;
834  }
835 
836  public function getResultProperties() {
837  return array(
838  '' => array(
839  'result' => array(
841  'Success',
842  'Warning',
843  'Continue',
844  'Queued'
845  ),
846  ),
847  'filekey' => array(
848  ApiBase::PROP_TYPE => 'string',
849  ApiBase::PROP_NULLABLE => true
850  ),
851  'sessionkey' => array(
852  ApiBase::PROP_TYPE => 'string',
853  ApiBase::PROP_NULLABLE => true
854  ),
855  'offset' => array(
856  ApiBase::PROP_TYPE => 'integer',
857  ApiBase::PROP_NULLABLE => true
858  ),
859  'statuskey' => array(
860  ApiBase::PROP_TYPE => 'string',
861  ApiBase::PROP_NULLABLE => true
862  ),
863  'filename' => array(
864  ApiBase::PROP_TYPE => 'string',
865  ApiBase::PROP_NULLABLE => true
866  )
867  )
868  );
869  }
870 
871  public function getDescription() {
872  return array(
873  'Upload a file, or get the status of pending uploads. Several methods are available:',
874  ' * Upload file contents directly, using the "file" parameter',
875  ' * Have the MediaWiki server fetch a file from a URL, using the "url" parameter',
876  ' * Complete an earlier upload that failed due to warnings, using the "filekey" parameter',
877  'Note that the HTTP POST must be done as a file upload (i.e. using multipart/form-data) when',
878  'sending the "file". Also you must get and send an edit token before doing any upload stuff.'
879  );
880  }
881 
882  public function getPossibleErrors() {
883  return array_merge( parent::getPossibleErrors(),
884  $this->getRequireOnlyOneParameterErrorMessages( array( 'filekey', 'file', 'url', 'statuskey' ) ),
885  array(
886  array( 'uploaddisabled' ),
887  array( 'invalid-file-key' ),
888  array( 'uploaddisabled' ),
889  array( 'mustbeloggedin', 'upload' ),
890  array( 'badaccess-groups' ),
891  array( 'code' => 'fetchfileerror', 'info' => '' ),
892  array( 'code' => 'nomodule', 'info' => 'No upload module set' ),
893  array( 'code' => 'empty-file', 'info' => 'The file you submitted was empty' ),
894  array( 'code' => 'filetype-missing', 'info' => 'The file is missing an extension' ),
895  array( 'code' => 'filename-tooshort', 'info' => 'The filename is too short' ),
896  array( 'code' => 'overwrite', 'info' => 'Overwriting an existing file is not allowed' ),
897  array( 'code' => 'stashfailed', 'info' => 'Stashing temporary file failed' ),
898  array( 'code' => 'publishfailed', 'info' => 'Publishing of stashed file failed' ),
899  array( 'code' => 'internal-error', 'info' => 'An internal error occurred' ),
900  array( 'code' => 'asynccopyuploaddisabled', 'info' => 'Asynchronous copy uploads disabled' ),
901  array( 'code' => 'stasherror', 'info' => 'An upload stash error occurred' ),
902  array( 'fileexists-forbidden' ),
903  array( 'fileexists-shared-forbidden' ),
904  )
905  );
906  }
907 
908  public function needsToken() {
909  return true;
910  }
911 
912  public function getTokenSalt() {
913  return '';
914  }
915 
916  public function getExamples() {
917  return array(
918  'api.php?action=upload&filename=Wiki.png' .
919  '&url=http%3A//upload.wikimedia.org/wikipedia/en/b/bc/Wiki.png'
920  => 'Upload from a URL',
921  'api.php?action=upload&filename=Wiki.png&filekey=filekey&ignorewarnings=1'
922  => 'Complete an upload that failed due to warnings',
923  );
924  }
925 
926  public function getHelpUrls() {
927  return 'https://www.mediawiki.org/wiki/API:Upload';
928  }
929 }
ApiUpload\getChunkResult
getChunkResult( $warnings)
Get the result of a chunk upload.
Definition: ApiUpload.php:201
ContextSource\getConfig
getConfig()
Get the Config object.
Definition: ContextSource.php:67
ApiUpload\selectUploadModule
selectUploadModule()
Select an upload module and set it to mUpload.
Definition: ApiUpload.php:370
Title\makeTitle
static & makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:398
$result
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. $reader:XMLReader object $logInfo:Array of information Return false to stop further processing of the tag 'ImportHandlePageXMLTag':When parsing a XML tag in a page. $reader:XMLReader object $pageInfo:Array of information Return false to stop further processing of the tag 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information Return false to stop further processing of the tag 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. $reader:XMLReader object Return false to stop further processing of the tag 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. $reader:XMLReader object $revisionInfo:Array of information Return false to stop further processing of the tag 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. $title:Title object for the current page $request:WebRequest $ignoreRedirect:boolean to skip redirect check $target:Title/string of redirect target $article:Article object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) $article:article(object) being checked 'IsTrustedProxy':Override the result of wfIsTrustedProxy() $ip:IP being check $result:Change this value to override the result of wfIsTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of User::isValidEmailAddr(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetMagic':DEPRECATED, use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetSpecialPageAliases':DEPRECATED, use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Associative array mapping language codes to prefixed links of the form "language:title". & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LinkBegin':Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1528
ContextSource\getContext
getContext()
Get the RequestContext object.
Definition: ContextSource.php:40
$request
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled also a ContextSource error or success you ll probably need to make sure the header is varied on WebRequest $request
Definition: hooks.txt:1961
ApiUpload\isWriteMode
isWriteMode()
Indicates whether this module requires write mode.
Definition: ApiUpload.php:739
php
skin txt MediaWiki includes four core it has been set as the default in MediaWiki since the replacing Monobook it had been been the default skin since before being replaced by Vector largely rewritten in while keeping its appearance Several legacy skins were removed in the as the burden of supporting them became too heavy to bear Those in etc for skin dependent CSS etc for skin dependent JavaScript These can also be customised on a per user by etc This feature has led to a wide variety of user styles becoming that gallery is a good place to ending in php
Definition: skin.txt:62
ApiBase\PARAM_REQUIRED
const PARAM_REQUIRED
Definition: ApiBase.php:62
ApiUpload\getStashResult
getStashResult( $warnings)
Get Stash Result, throws an exception if the file could not be stashed.
Definition: ApiUpload.php:157
ApiBase\parseMsg
parseMsg( $error)
Return the error message related to a certain array.
Definition: ApiBase.php:2002
ApiUpload\getPossibleErrors
getPossibleErrors()
Returns a list of all possible errors returned by the module.
Definition: ApiUpload.php:881
ApiUpload\verifyUpload
verifyUpload()
Performs file verification, dies on error.
Definition: ApiUpload.php:515
ApiBase\dieUsageMsg
dieUsageMsg( $error)
Output the error message related to a certain array.
Definition: ApiBase.php:1953
ApiBase\PARAM_TYPE
const PARAM_TYPE
Definition: ApiBase.php:50
ApiBase\getResult
getResult()
Get the result object.
Definition: ApiBase.php:205
ApiUpload\getWarningsResult
getWarningsResult( $warnings)
Get Warnings Result.
Definition: ApiUpload.php:180
Status\newGood
static newGood( $value=null)
Factory function for good results.
Definition: Status.php:77
NS_FILE
const NS_FILE
Definition: Defines.php:85
$params
$params
Definition: styleTest.css.php:40
ApiUpload\execute
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
Definition: ApiUpload.php:35
ApiUpload\getExamples
getExamples()
Returns usage examples for this module.
Definition: ApiUpload.php:915
ApiUpload\getApiWarnings
getApiWarnings()
Check warnings.
Definition: ApiUpload.php:598
ApiUpload\getAllowedParams
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition: ApiUpload.php:743
UploadFromStash
Implements uploading from previously stored file.
Definition: UploadFromStash.php:30
ApiUpload\getTokenSalt
getTokenSalt()
Returns the token salt if there is one, '' if the module doesn't require a salt, else false if the mo...
Definition: ApiUpload.php:911
ApiUpload\$mParams
$mParams
Definition: ApiUpload.php:33
ContextSource\getUser
getUser()
Get the User object.
Definition: ContextSource.php:132
ApiBase
This abstract class implements many basic API functions, and is the base of all API classes.
Definition: ApiBase.php:42
UploadFromUrlJob\getSessionData
static & getSessionData( $key)
Definition: UploadFromUrlJob.php:178
PublishStashedFileJob
Upload a file from the upload stash into the local file repo.
Definition: PublishStashedFileJob.php:29
UploadFromUrl\initialize
initialize( $name, $url, $async=false)
Entry point for API upload.
Definition: UploadFromUrl.php:134
ApiBase\PARAM_DEPRECATED
const PARAM_DEPRECATED
Definition: ApiBase.php:60
ApiBase\PARAM_MIN
const PARAM_MIN
Definition: ApiBase.php:56
MWException
MediaWiki exception.
Definition: MWException.php:26
UploadFromUrl
Implements uploading from a HTTP resource.
Definition: UploadFromUrl.php:31
ApiUpload\dieRecoverableError
dieRecoverableError( $error, $parameter, $data=array())
Throw an error that the user can recover from by providing a better value for $parameter.
Definition: ApiUpload.php:350
UploadFromUrl\isEnabled
static isEnabled()
Checks if the upload from URL feature is enabled.
Definition: UploadFromUrl.php:59
ApiBase\PARAM_MAX
const PARAM_MAX
Definition: ApiBase.php:52
ApiUpload\getContextResult
getContextResult()
Get an upload result based on upload context.
Definition: ApiUpload.php:128
array
the array() calling protocol came about after MediaWiki 1.4rc1.
List of Api Query prop modules.
ApiUpload
Definition: ApiUpload.php:30
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
$wgFileExtensions
if(! $wgHtml5Version && $wgAllowRdfaAttributes) $wgFileExtensions
Definition: Setup.php:369
ApiBase\getRequireOnlyOneParameterErrorMessages
getRequireOnlyOneParameterErrorMessages( $params)
Generates the possible errors requireOnlyOneParameter() can die with.
Definition: ApiBase.php:768
ApiBase\PROP_TYPE
const PROP_TYPE
Definition: ApiBase.php:74
ApiUpload\getParamDescription
getParamDescription()
Returns an array of parameter descriptions.
Definition: ApiUpload.php:803
ApiBase\getWatchlistValue
getWatchlistValue( $watchlist, $titleObj, $userOption=null)
Return true if we're to watch the page, false if not, null if no change.
Definition: ApiBase.php:933
ApiUpload\needsToken
needsToken()
Returns whether this module requires a token to execute It is used to show possible errors in action=...
Definition: ApiUpload.php:907
$ok
$ok
Definition: UtfNormalTest.php:71
wfDebug
wfDebug( $text, $dest='all')
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:980
ApiBase\extractRequestParams
extractRequestParams( $parseLimit=true)
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:707
UploadFromStash\isValidKey
static isValidKey( $key)
Definition: UploadFromStash.php:74
UploadFromUrl\isAllowedUrl
static isAllowedUrl( $url)
Checks whether the URL is not allowed.
Definition: UploadFromUrl.php:115
ApiUpload\getResultProperties
getResultProperties()
Returns possible properties in the result, grouped by the value of the prop parameter that shows them...
Definition: ApiUpload.php:835
ApiBase\dieUsage
dieUsage( $description, $errorCode, $httpRespCode=0, $extradata=null)
Throw a UsageException, which will (if uncaught) call the main module's error handler and die with an...
Definition: ApiBase.php:1383
AssembleUploadChunksJob
Assemble the segments of a chunked upload.
Definition: AssembleUploadChunksJob.php:29
ApiBase\requireOnlyOneParameter
requireOnlyOneParameter( $params)
Die if none or more than one of a certain set of parameters is set and not false.
Definition: ApiBase.php:742
ApiUpload\performStash
performStash()
Stash the file and return the file key Also re-raises exceptions with slightly more informative messa...
Definition: ApiUpload.php:324
ApiUpload\getHelpUrls
getHelpUrls()
Definition: ApiUpload.php:925
ApiBase\PROP_NULLABLE
const PROP_NULLABLE
Definition: ApiBase.php:76
ApiUpload\mustBePosted
mustBePosted()
Indicates whether this module must be called with a POST request.
Definition: ApiUpload.php:735
ApiUpload\performUpload
performUpload( $warnings)
Perform the actual upload.
Definition: ApiUpload.php:639
UploadFromUrl\isAllowedHost
static isAllowedHost( $url)
Checks whether the URL is for an allowed host The domains in the whitelist can include wildcard chara...
Definition: UploadFromUrl.php:72
$user
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a account $user
Definition: hooks.txt:237
$file
if(PHP_SAPI !='cli') $file
Definition: UtfNormalTest2.php:30
ApiUpload\$mUpload
UploadBase $mUpload
Definition: ApiUpload.php:31
ContextSource\exportSession
exportSession()
Export the resolved user IP, HTTP headers, user ID, and session ID.
Definition: ContextSource.php:188
JobQueueGroup\singleton
static singleton( $wiki=false)
Definition: JobQueueGroup.php:61
ApiBase\PARAM_DFLT
const PARAM_DFLT
Definition: ApiBase.php:46
ApiUpload\checkPermissions
checkPermissions( $user)
Checks that the user has permissions to perform this upload.
Definition: ApiUpload.php:499
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
ApiBase\getModuleName
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:148
ApiBase\getMain
getMain()
Get the main module.
Definition: ApiBase.php:188
ApiUpload\checkAsyncDownloadEnabled
checkAsyncDownloadEnabled()
Checks if asynchronous copy uploads are enabled and throws an error if they are not.
Definition: ApiUpload.php:728
UploadFromChunks
Implements uploading from chunks.
Definition: UploadFromChunks.php:30
ApiUpload\transformWarnings
transformWarnings( $warnings)
Definition: ApiUpload.php:604
$error
usually copyright or history_copyright This message must be in HTML not wikitext $subpages will be ignored and the rest of subPageSubtitle() will run. 'SkinTemplateBuildNavUrlsNav_urlsAfterPermalink' whether MediaWiki currently thinks this is a CSS JS page Hooks may change this value to override the return value of Title::isCssOrJsPage(). 'TitleIsAlwaysKnown' whether MediaWiki currently thinks this page is known isMovable() always returns false. $title whether MediaWiki currently thinks this page is movable Hooks may change this value to override the return value of Title::isMovable(). 'TitleIsWikitextPage' whether MediaWiki currently thinks this is a wikitext page Hooks may change this value to override the return value of Title::isWikitextPage() 'TitleMove' use UploadVerification and UploadVerifyFile instead where the first element is the message key and the remaining elements are used as parameters to the message based on mime etc Preferred in most cases over UploadVerification object with all info about the upload string as detected by MediaWiki Handlers will typically only apply for specific mime types object & $error
Definition: hooks.txt:2584
UploadFromFile
Implements regular file uploads.
Definition: UploadFromFile.php:30
ApiUpload\getDescription
getDescription()
Returns the description string for this module.
Definition: ApiUpload.php:870
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:1632
UploadStashException
Definition: UploadStash.php:679
ApiUpload\checkVerification
checkVerification(array $verification)
Performs file verification, dies on error.
Definition: ApiUpload.php:527