MediaWiki  REL1_23
SpecialUpload.php
Go to the documentation of this file.
00001 <?php
00031 class SpecialUpload extends SpecialPage {
00037     public function __construct( $request = null ) {
00038         parent::__construct( 'Upload', 'upload' );
00039     }
00040 
00044     public $mRequest;
00045     public $mSourceType;
00046 
00048     public $mUpload;
00049 
00051     public $mLocalFile;
00052     public $mUploadClicked;
00053 
00057     public $mDesiredDestName;
00058     public $mComment;
00059     public $mLicense;
00060 
00063     public $mIgnoreWarning;
00064     public $mWatchthis;
00065     public $mCopyrightStatus;
00066     public $mCopyrightSource;
00067 
00070     public $mDestWarningAck;
00071 
00073     public $mForReUpload;
00074 
00076     public $mCancelUpload;
00077     public $mTokenOk;
00078 
00080     public $mUploadSuccessful = false;
00081 
00083     public $uploadFormTextTop;
00084     public $uploadFormTextAfterSummary;
00085 
00089     protected function loadRequest() {
00090         $this->mRequest = $request = $this->getRequest();
00091         $this->mSourceType = $request->getVal( 'wpSourceType', 'file' );
00092         $this->mUpload = UploadBase::createFromRequest( $request );
00093         $this->mUploadClicked = $request->wasPosted()
00094             && ( $request->getCheck( 'wpUpload' )
00095                 || $request->getCheck( 'wpUploadIgnoreWarning' ) );
00096 
00097         // Guess the desired name from the filename if not provided
00098         $this->mDesiredDestName = $request->getText( 'wpDestFile' );
00099         if ( !$this->mDesiredDestName && $request->getFileName( 'wpUploadFile' ) !== null ) {
00100             $this->mDesiredDestName = $request->getFileName( 'wpUploadFile' );
00101         }
00102         $this->mLicense = $request->getText( 'wpLicense' );
00103 
00104         $this->mDestWarningAck = $request->getText( 'wpDestFileWarningAck' );
00105         $this->mIgnoreWarning = $request->getCheck( 'wpIgnoreWarning' )
00106             || $request->getCheck( 'wpUploadIgnoreWarning' );
00107         $this->mWatchthis = $request->getBool( 'wpWatchthis' ) && $this->getUser()->isLoggedIn();
00108         $this->mCopyrightStatus = $request->getText( 'wpUploadCopyStatus' );
00109         $this->mCopyrightSource = $request->getText( 'wpUploadSource' );
00110 
00111         $this->mForReUpload = $request->getBool( 'wpForReUpload' ); // updating a file
00112 
00113         $commentDefault = '';
00114         $commentMsg = wfMessage( 'upload-default-description' )->inContentLanguage();
00115         if ( !$this->mForReUpload && !$commentMsg->isDisabled() ) {
00116             $commentDefault = $commentMsg->plain();
00117         }
00118         $this->mComment = $request->getText( 'wpUploadDescription', $commentDefault );
00119 
00120         $this->mCancelUpload = $request->getCheck( 'wpCancelUpload' )
00121             || $request->getCheck( 'wpReUpload' ); // b/w compat
00122 
00123         // If it was posted check for the token (no remote POST'ing with user credentials)
00124         $token = $request->getVal( 'wpEditToken' );
00125         $this->mTokenOk = $this->getUser()->matchEditToken( $token );
00126 
00127         $this->uploadFormTextTop = '';
00128         $this->uploadFormTextAfterSummary = '';
00129     }
00130 
00139     public function userCanExecute( User $user ) {
00140         return UploadBase::isEnabled() && parent::userCanExecute( $user );
00141     }
00142 
00146     public function execute( $par ) {
00147         $this->setHeaders();
00148         $this->outputHeader();
00149 
00150         # Check uploading enabled
00151         if ( !UploadBase::isEnabled() ) {
00152             throw new ErrorPageError( 'uploaddisabled', 'uploaddisabledtext' );
00153         }
00154 
00155         # Check permissions
00156         $user = $this->getUser();
00157         $permissionRequired = UploadBase::isAllowed( $user );
00158         if ( $permissionRequired !== true ) {
00159             throw new PermissionsError( $permissionRequired );
00160         }
00161 
00162         # Check blocks
00163         if ( $user->isBlocked() ) {
00164             throw new UserBlockedError( $user->getBlock() );
00165         }
00166 
00167         # Check whether we actually want to allow changing stuff
00168         $this->checkReadOnly();
00169 
00170         $this->loadRequest();
00171 
00172         # Unsave the temporary file in case this was a cancelled upload
00173         if ( $this->mCancelUpload ) {
00174             if ( !$this->unsaveUploadedFile() ) {
00175                 # Something went wrong, so unsaveUploadedFile showed a warning
00176                 return;
00177             }
00178         }
00179 
00180         # Process upload or show a form
00181         if (
00182             $this->mTokenOk && !$this->mCancelUpload &&
00183             ( $this->mUpload && $this->mUploadClicked )
00184         ) {
00185             $this->processUpload();
00186         } else {
00187             # Backwards compatibility hook
00188             if ( !wfRunHooks( 'UploadForm:initial', array( &$this ) ) ) {
00189                 wfDebug( "Hook 'UploadForm:initial' broke output of the upload form\n" );
00190 
00191                 return;
00192             }
00193             $this->showUploadForm( $this->getUploadForm() );
00194         }
00195 
00196         # Cleanup
00197         if ( $this->mUpload ) {
00198             $this->mUpload->cleanupTempFile();
00199         }
00200     }
00201 
00207     protected function showUploadForm( $form ) {
00208         # Add links if file was previously deleted
00209         if ( $this->mDesiredDestName ) {
00210             $this->showViewDeletedLinks();
00211         }
00212 
00213         if ( $form instanceof HTMLForm ) {
00214             $form->show();
00215         } else {
00216             $this->getOutput()->addHTML( $form );
00217         }
00218     }
00219 
00228     protected function getUploadForm( $message = '', $sessionKey = '', $hideIgnoreWarning = false ) {
00229         # Initialize form
00230         $context = new DerivativeContext( $this->getContext() );
00231         $context->setTitle( $this->getPageTitle() ); // Remove subpage
00232         $form = new UploadForm( array(
00233             'watch' => $this->getWatchCheck(),
00234             'forreupload' => $this->mForReUpload,
00235             'sessionkey' => $sessionKey,
00236             'hideignorewarning' => $hideIgnoreWarning,
00237             'destwarningack' => (bool)$this->mDestWarningAck,
00238 
00239             'description' => $this->mComment,
00240             'texttop' => $this->uploadFormTextTop,
00241             'textaftersummary' => $this->uploadFormTextAfterSummary,
00242             'destfile' => $this->mDesiredDestName,
00243         ), $context );
00244 
00245         # Check the token, but only if necessary
00246         if (
00247             !$this->mTokenOk && !$this->mCancelUpload &&
00248             ( $this->mUpload && $this->mUploadClicked )
00249         ) {
00250             $form->addPreText( $this->msg( 'session_fail_preview' )->parse() );
00251         }
00252 
00253         # Give a notice if the user is uploading a file that has been deleted or moved
00254         # Note that this is independent from the message 'filewasdeleted' that requires JS
00255         $desiredTitleObj = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName );
00256         $delNotice = ''; // empty by default
00257         if ( $desiredTitleObj instanceof Title && !$desiredTitleObj->exists() ) {
00258             LogEventsList::showLogExtract( $delNotice, array( 'delete', 'move' ),
00259                 $desiredTitleObj,
00260                 '', array( 'lim' => 10,
00261                     'conds' => array( "log_action != 'revision'" ),
00262                     'showIfEmpty' => false,
00263                     'msgKey' => array( 'upload-recreate-warning' ) )
00264             );
00265         }
00266         $form->addPreText( $delNotice );
00267 
00268         # Add text to form
00269         $form->addPreText( '<div id="uploadtext">' .
00270             $this->msg( 'uploadtext', array( $this->mDesiredDestName ) )->parseAsBlock() .
00271             '</div>' );
00272         # Add upload error message
00273         $form->addPreText( $message );
00274 
00275         # Add footer to form
00276         $uploadFooter = $this->msg( 'uploadfooter' );
00277         if ( !$uploadFooter->isDisabled() ) {
00278             $form->addPostText( '<div id="mw-upload-footer-message">'
00279                 . $uploadFooter->parseAsBlock() . "</div>\n" );
00280         }
00281 
00282         return $form;
00283     }
00284 
00288     protected function showViewDeletedLinks() {
00289         $title = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName );
00290         $user = $this->getUser();
00291         // Show a subtitle link to deleted revisions (to sysops et al only)
00292         if ( $title instanceof Title ) {
00293             $count = $title->isDeleted();
00294             if ( $count > 0 && $user->isAllowed( 'deletedhistory' ) ) {
00295                 $restorelink = Linker::linkKnown(
00296                     SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedText() ),
00297                     $this->msg( 'restorelink' )->numParams( $count )->escaped()
00298                 );
00299                 $link = $this->msg( $user->isAllowed( 'delete' ) ? 'thisisdeleted' : 'viewdeleted' )
00300                     ->rawParams( $restorelink )->parseAsBlock();
00301                 $this->getOutput()->addHTML( "<div id=\"contentSub2\">{$link}</div>" );
00302             }
00303         }
00304     }
00305 
00317     protected function showRecoverableUploadError( $message ) {
00318         $sessionKey = $this->mUpload->stashSession();
00319         $message = '<h2>' . $this->msg( 'uploaderror' )->escaped() . "</h2>\n" .
00320             '<div class="error">' . $message . "</div>\n";
00321 
00322         $form = $this->getUploadForm( $message, $sessionKey );
00323         $form->setSubmitText( $this->msg( 'upload-tryagain' )->escaped() );
00324         $this->showUploadForm( $form );
00325     }
00326 
00335     protected function showUploadWarning( $warnings ) {
00336         # If there are no warnings, or warnings we can ignore, return early.
00337         # mDestWarningAck is set when some javascript has shown the warning
00338         # to the user. mForReUpload is set when the user clicks the "upload a
00339         # new version" link.
00340         if ( !$warnings || ( count( $warnings ) == 1
00341             && isset( $warnings['exists'] )
00342             && ( $this->mDestWarningAck || $this->mForReUpload ) )
00343         ) {
00344             return false;
00345         }
00346 
00347         $sessionKey = $this->mUpload->stashSession();
00348 
00349         $warningHtml = '<h2>' . $this->msg( 'uploadwarning' )->escaped() . "</h2>\n"
00350             . '<ul class="warning">';
00351         foreach ( $warnings as $warning => $args ) {
00352             if ( $warning == 'badfilename' ) {
00353                 $this->mDesiredDestName = Title::makeTitle( NS_FILE, $args )->getText();
00354             }
00355             if ( $warning == 'exists' ) {
00356                 $msg = "\t<li>" . self::getExistsWarning( $args ) . "</li>\n";
00357             } elseif ( $warning == 'duplicate' ) {
00358                 $msg = $this->getDupeWarning( $args );
00359             } elseif ( $warning == 'duplicate-archive' ) {
00360                 if ( $args === '' ) {
00361                     $msg = "\t<li>" . $this->msg( 'file-deleted-duplicate-notitle' )->parse()
00362                         . "</li>\n";
00363                 } else {
00364                     $msg = "\t<li>" . $this->msg( 'file-deleted-duplicate',
00365                             Title::makeTitle( NS_FILE, $args )->getPrefixedText() )->parse()
00366                         . "</li>\n";
00367                 }
00368             } else {
00369                 if ( $args === true ) {
00370                     $args = array();
00371                 } elseif ( !is_array( $args ) ) {
00372                     $args = array( $args );
00373                 }
00374                 $msg = "\t<li>" . $this->msg( $warning, $args )->parse() . "</li>\n";
00375             }
00376             $warningHtml .= $msg;
00377         }
00378         $warningHtml .= "</ul>\n";
00379         $warningHtml .= $this->msg( 'uploadwarning-text' )->parseAsBlock();
00380 
00381         $form = $this->getUploadForm( $warningHtml, $sessionKey, /* $hideIgnoreWarning */ true );
00382         $form->setSubmitText( $this->msg( 'upload-tryagain' )->text() );
00383         $form->addButton( 'wpUploadIgnoreWarning', $this->msg( 'ignorewarning' )->text() );
00384         $form->addButton( 'wpCancelUpload', $this->msg( 'reuploaddesc' )->text() );
00385 
00386         $this->showUploadForm( $form );
00387 
00388         # Indicate that we showed a form
00389         return true;
00390     }
00391 
00397     protected function showUploadError( $message ) {
00398         $message = '<h2>' . $this->msg( 'uploadwarning' )->escaped() . "</h2>\n" .
00399             '<div class="error">' . $message . "</div>\n";
00400         $this->showUploadForm( $this->getUploadForm( $message ) );
00401     }
00402 
00407     protected function processUpload() {
00408         // Fetch the file if required
00409         $status = $this->mUpload->fetchFile();
00410         if ( !$status->isOK() ) {
00411             $this->showUploadError( $this->getOutput()->parse( $status->getWikiText() ) );
00412 
00413             return;
00414         }
00415 
00416         if ( !wfRunHooks( 'UploadForm:BeforeProcessing', array( &$this ) ) ) {
00417             wfDebug( "Hook 'UploadForm:BeforeProcessing' broke processing the file.\n" );
00418             // This code path is deprecated. If you want to break upload processing
00419             // do so by hooking into the appropriate hooks in UploadBase::verifyUpload
00420             // and UploadBase::verifyFile.
00421             // If you use this hook to break uploading, the user will be returned
00422             // an empty form with no error message whatsoever.
00423             return;
00424         }
00425 
00426         // Upload verification
00427         $details = $this->mUpload->verifyUpload();
00428         if ( $details['status'] != UploadBase::OK ) {
00429             $this->processVerificationError( $details );
00430 
00431             return;
00432         }
00433 
00434         // Verify permissions for this title
00435         $permErrors = $this->mUpload->verifyTitlePermissions( $this->getUser() );
00436         if ( $permErrors !== true ) {
00437             $code = array_shift( $permErrors[0] );
00438             $this->showRecoverableUploadError( $this->msg( $code, $permErrors[0] )->parse() );
00439 
00440             return;
00441         }
00442 
00443         $this->mLocalFile = $this->mUpload->getLocalFile();
00444 
00445         // Check warnings if necessary
00446         if ( !$this->mIgnoreWarning ) {
00447             $warnings = $this->mUpload->checkWarnings();
00448             if ( $this->showUploadWarning( $warnings ) ) {
00449                 return;
00450             }
00451         }
00452 
00453         // Get the page text if this is not a reupload
00454         if ( !$this->mForReUpload ) {
00455             $pageText = self::getInitialPageText( $this->mComment, $this->mLicense,
00456                 $this->mCopyrightStatus, $this->mCopyrightSource );
00457         } else {
00458             $pageText = false;
00459         }
00460 
00461         $status = $this->mUpload->performUpload(
00462             $this->mComment,
00463             $pageText,
00464             $this->mWatchthis,
00465             $this->getUser()
00466         );
00467 
00468         if ( !$status->isGood() ) {
00469             $this->showUploadError( $this->getOutput()->parse( $status->getWikiText() ) );
00470 
00471             return;
00472         }
00473 
00474         // Success, redirect to description page
00475         $this->mUploadSuccessful = true;
00476         wfRunHooks( 'SpecialUploadComplete', array( &$this ) );
00477         $this->getOutput()->redirect( $this->mLocalFile->getTitle()->getFullURL() );
00478     }
00479 
00488     public static function getInitialPageText( $comment = '', $license = '',
00489         $copyStatus = '', $source = ''
00490     ) {
00491         global $wgUseCopyrightUpload, $wgForceUIMsgAsContentMsg;
00492 
00493         $msg = array();
00494         /* These messages are transcluded into the actual text of the description page.
00495          * Thus, forcing them as content messages makes the upload to produce an int: template
00496          * instead of hardcoding it there in the uploader language.
00497          */
00498         foreach ( array( 'license-header', 'filedesc', 'filestatus', 'filesource' ) as $msgName ) {
00499             if ( in_array( $msgName, (array)$wgForceUIMsgAsContentMsg ) ) {
00500                 $msg[$msgName] = "{{int:$msgName}}";
00501             } else {
00502                 $msg[$msgName] = wfMessage( $msgName )->inContentLanguage()->text();
00503             }
00504         }
00505 
00506         if ( $wgUseCopyrightUpload ) {
00507             $licensetxt = '';
00508             if ( $license != '' ) {
00509                 $licensetxt = '== ' . $msg['license-header'] . " ==\n" . '{{' . $license . '}}' . "\n";
00510             }
00511             $pageText = '== ' . $msg['filedesc'] . " ==\n" . $comment . "\n" .
00512                 '== ' . $msg['filestatus'] . " ==\n" . $copyStatus . "\n" .
00513                 "$licensetxt" .
00514                 '== ' . $msg['filesource'] . " ==\n" . $source;
00515         } else {
00516             if ( $license != '' ) {
00517                 $filedesc = $comment == '' ? '' : '== ' . $msg['filedesc'] . " ==\n" . $comment . "\n";
00518                     $pageText = $filedesc .
00519                     '== ' . $msg['license-header'] . " ==\n" . '{{' . $license . '}}' . "\n";
00520             } else {
00521                 $pageText = $comment;
00522             }
00523         }
00524 
00525         return $pageText;
00526     }
00527 
00540     protected function getWatchCheck() {
00541         if ( $this->getUser()->getOption( 'watchdefault' ) ) {
00542             // Watch all edits!
00543             return true;
00544         }
00545 
00546         $desiredTitleObj = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName );
00547         if ( $desiredTitleObj instanceof Title && $this->getUser()->isWatched( $desiredTitleObj ) ) {
00548             // Already watched, don't change that
00549             return true;
00550         }
00551 
00552         $local = wfLocalFile( $this->mDesiredDestName );
00553         if ( $local && $local->exists() ) {
00554             // We're uploading a new version of an existing file.
00555             // No creation, so don't watch it if we're not already.
00556             return false;
00557         } else {
00558             // New page should get watched if that's our option.
00559             return $this->getUser()->getOption( 'watchcreations' );
00560         }
00561     }
00562 
00569     protected function processVerificationError( $details ) {
00570         global $wgFileExtensions;
00571 
00572         switch ( $details['status'] ) {
00573 
00575             case UploadBase::MIN_LENGTH_PARTNAME:
00576                 $this->showRecoverableUploadError( $this->msg( 'minlength1' )->escaped() );
00577                 break;
00578             case UploadBase::ILLEGAL_FILENAME:
00579                 $this->showRecoverableUploadError( $this->msg( 'illegalfilename',
00580                     $details['filtered'] )->parse() );
00581                 break;
00582             case UploadBase::FILENAME_TOO_LONG:
00583                 $this->showRecoverableUploadError( $this->msg( 'filename-toolong' )->escaped() );
00584                 break;
00585             case UploadBase::FILETYPE_MISSING:
00586                 $this->showRecoverableUploadError( $this->msg( 'filetype-missing' )->parse() );
00587                 break;
00588             case UploadBase::WINDOWS_NONASCII_FILENAME:
00589                 $this->showRecoverableUploadError( $this->msg( 'windows-nonascii-filename' )->parse() );
00590                 break;
00591 
00593             case UploadBase::EMPTY_FILE:
00594                 $this->showUploadError( $this->msg( 'emptyfile' )->escaped() );
00595                 break;
00596             case UploadBase::FILE_TOO_LARGE:
00597                 $this->showUploadError( $this->msg( 'largefileserver' )->escaped() );
00598                 break;
00599             case UploadBase::FILETYPE_BADTYPE:
00600                 $msg = $this->msg( 'filetype-banned-type' );
00601                 if ( isset( $details['blacklistedExt'] ) ) {
00602                     $msg->params( $this->getLanguage()->commaList( $details['blacklistedExt'] ) );
00603                 } else {
00604                     $msg->params( $details['finalExt'] );
00605                 }
00606                 $extensions = array_unique( $wgFileExtensions );
00607                 $msg->params( $this->getLanguage()->commaList( $extensions ),
00608                     count( $extensions ) );
00609 
00610                 // Add PLURAL support for the first parameter. This results
00611                 // in a bit unlogical parameter sequence, but does not break
00612                 // old translations
00613                 if ( isset( $details['blacklistedExt'] ) ) {
00614                     $msg->params( count( $details['blacklistedExt'] ) );
00615                 } else {
00616                     $msg->params( 1 );
00617                 }
00618 
00619                 $this->showUploadError( $msg->parse() );
00620                 break;
00621             case UploadBase::VERIFICATION_ERROR:
00622                 unset( $details['status'] );
00623                 $code = array_shift( $details['details'] );
00624                 $this->showUploadError( $this->msg( $code, $details['details'] )->parse() );
00625                 break;
00626             case UploadBase::HOOK_ABORTED:
00627                 if ( is_array( $details['error'] ) ) { # allow hooks to return error details in an array
00628                     $args = $details['error'];
00629                     $error = array_shift( $args );
00630                 } else {
00631                     $error = $details['error'];
00632                     $args = null;
00633                 }
00634 
00635                 $this->showUploadError( $this->msg( $error, $args )->parse() );
00636                 break;
00637             default:
00638                 throw new MWException( __METHOD__ . ": Unknown value `{$details['status']}`" );
00639         }
00640     }
00641 
00647     protected function unsaveUploadedFile() {
00648         if ( !( $this->mUpload instanceof UploadFromStash ) ) {
00649             return true;
00650         }
00651         $success = $this->mUpload->unsaveUploadedFile();
00652         if ( !$success ) {
00653             $this->getOutput()->showFileDeleteError( $this->mUpload->getTempPath() );
00654 
00655             return false;
00656         } else {
00657             return true;
00658         }
00659     }
00660 
00661     /*** Functions for formatting warnings ***/
00662 
00670     public static function getExistsWarning( $exists ) {
00671         if ( !$exists ) {
00672             return '';
00673         }
00674 
00675         $file = $exists['file'];
00676         $filename = $file->getTitle()->getPrefixedText();
00677         $warning = '';
00678 
00679         if ( $exists['warning'] == 'exists' ) {
00680             // Exact match
00681             $warning = wfMessage( 'fileexists', $filename )->parse();
00682         } elseif ( $exists['warning'] == 'page-exists' ) {
00683             // Page exists but file does not
00684             $warning = wfMessage( 'filepageexists', $filename )->parse();
00685         } elseif ( $exists['warning'] == 'exists-normalized' ) {
00686             $warning = wfMessage( 'fileexists-extension', $filename,
00687                 $exists['normalizedFile']->getTitle()->getPrefixedText() )->parse();
00688         } elseif ( $exists['warning'] == 'thumb' ) {
00689             // Swapped argument order compared with other messages for backwards compatibility
00690             $warning = wfMessage( 'fileexists-thumbnail-yes',
00691                 $exists['thumbFile']->getTitle()->getPrefixedText(), $filename )->parse();
00692         } elseif ( $exists['warning'] == 'thumb-name' ) {
00693             // Image w/o '180px-' does not exists, but we do not like these filenames
00694             $name = $file->getName();
00695             $badPart = substr( $name, 0, strpos( $name, '-' ) + 1 );
00696             $warning = wfMessage( 'file-thumbnail-no', $badPart )->parse();
00697         } elseif ( $exists['warning'] == 'bad-prefix' ) {
00698             $warning = wfMessage( 'filename-bad-prefix', $exists['prefix'] )->parse();
00699         } elseif ( $exists['warning'] == 'was-deleted' ) {
00700             # If the file existed before and was deleted, warn the user of this
00701             $ltitle = SpecialPage::getTitleFor( 'Log' );
00702             $llink = Linker::linkKnown(
00703                 $ltitle,
00704                 wfMessage( 'deletionlog' )->escaped(),
00705                 array(),
00706                 array(
00707                     'type' => 'delete',
00708                     'page' => $filename
00709                 )
00710             );
00711             $warning = wfMessage( 'filewasdeleted' )->rawParams( $llink )->parseAsBlock();
00712         }
00713 
00714         return $warning;
00715     }
00716 
00722     public function getDupeWarning( $dupes ) {
00723         if ( !$dupes ) {
00724             return '';
00725         }
00726 
00727         $gallery = ImageGalleryBase::factory();
00728         $gallery->setContext( $this->getContext() );
00729         $gallery->setShowBytes( false );
00730         foreach ( $dupes as $file ) {
00731             $gallery->add( $file->getTitle() );
00732         }
00733 
00734         return '<li>' .
00735             wfMessage( 'file-exists-duplicate' )->numParams( count( $dupes ) )->parse() .
00736             $gallery->toHtml() . "</li>\n";
00737     }
00738 
00739     protected function getGroupName() {
00740         return 'media';
00741     }
00742 }
00743 
00747 class UploadForm extends HTMLForm {
00748     protected $mWatch;
00749     protected $mForReUpload;
00750     protected $mSessionKey;
00751     protected $mHideIgnoreWarning;
00752     protected $mDestWarningAck;
00753     protected $mDestFile;
00754 
00755     protected $mComment;
00756     protected $mTextTop;
00757     protected $mTextAfterSummary;
00758 
00759     protected $mSourceIds;
00760 
00761     protected $mMaxFileSize = array();
00762 
00763     protected $mMaxUploadSize = array();
00764 
00765     public function __construct( array $options = array(), IContextSource $context = null ) {
00766         $this->mWatch = !empty( $options['watch'] );
00767         $this->mForReUpload = !empty( $options['forreupload'] );
00768         $this->mSessionKey = isset( $options['sessionkey'] ) ? $options['sessionkey'] : '';
00769         $this->mHideIgnoreWarning = !empty( $options['hideignorewarning'] );
00770         $this->mDestWarningAck = !empty( $options['destwarningack'] );
00771         $this->mDestFile = isset( $options['destfile'] ) ? $options['destfile'] : '';
00772 
00773         $this->mComment = isset( $options['description'] ) ?
00774             $options['description'] : '';
00775 
00776         $this->mTextTop = isset( $options['texttop'] )
00777             ? $options['texttop'] : '';
00778 
00779         $this->mTextAfterSummary = isset( $options['textaftersummary'] )
00780             ? $options['textaftersummary'] : '';
00781 
00782         $sourceDescriptor = $this->getSourceSection();
00783         $descriptor = $sourceDescriptor
00784             + $this->getDescriptionSection()
00785             + $this->getOptionsSection();
00786 
00787         wfRunHooks( 'UploadFormInitDescriptor', array( &$descriptor ) );
00788         parent::__construct( $descriptor, $context, 'upload' );
00789 
00790         # Set some form properties
00791         $this->setSubmitText( $this->msg( 'uploadbtn' )->text() );
00792         $this->setSubmitName( 'wpUpload' );
00793         # Used message keys: 'accesskey-upload', 'tooltip-upload'
00794         $this->setSubmitTooltip( 'upload' );
00795         $this->setId( 'mw-upload-form' );
00796 
00797         # Build a list of IDs for javascript insertion
00798         $this->mSourceIds = array();
00799         foreach ( $sourceDescriptor as $field ) {
00800             if ( !empty( $field['id'] ) ) {
00801                 $this->mSourceIds[] = $field['id'];
00802             }
00803         }
00804     }
00805 
00812     protected function getSourceSection() {
00813         global $wgCopyUploadsFromSpecialUpload;
00814 
00815         if ( $this->mSessionKey ) {
00816             return array(
00817                 'SessionKey' => array(
00818                     'type' => 'hidden',
00819                     'default' => $this->mSessionKey,
00820                 ),
00821                 'SourceType' => array(
00822                     'type' => 'hidden',
00823                     'default' => 'Stash',
00824                 ),
00825             );
00826         }
00827 
00828         $canUploadByUrl = UploadFromUrl::isEnabled()
00829             && UploadFromUrl::isAllowed( $this->getUser() )
00830             && $wgCopyUploadsFromSpecialUpload;
00831         $radio = $canUploadByUrl;
00832         $selectedSourceType = strtolower( $this->getRequest()->getText( 'wpSourceType', 'File' ) );
00833 
00834         $descriptor = array();
00835         if ( $this->mTextTop ) {
00836             $descriptor['UploadFormTextTop'] = array(
00837                 'type' => 'info',
00838                 'section' => 'source',
00839                 'default' => $this->mTextTop,
00840                 'raw' => true,
00841             );
00842         }
00843 
00844         $this->mMaxUploadSize['file'] = UploadBase::getMaxUploadSize( 'file' );
00845         # Limit to upload_max_filesize unless we are running under HipHop and
00846         # that setting doesn't exist
00847         if ( !wfIsHHVM() ) {
00848             $this->mMaxUploadSize['file'] = min( $this->mMaxUploadSize['file'],
00849                 wfShorthandToInteger( ini_get( 'upload_max_filesize' ) ),
00850                 wfShorthandToInteger( ini_get( 'post_max_size' ) )
00851             );
00852         }
00853 
00854         $descriptor['UploadFile'] = array(
00855             'class' => 'UploadSourceField',
00856             'section' => 'source',
00857             'type' => 'file',
00858             'id' => 'wpUploadFile',
00859             'radio-id' => 'wpSourceTypeFile',
00860             'label-message' => 'sourcefilename',
00861             'upload-type' => 'File',
00862             'radio' => &$radio,
00863             'help' => $this->msg( 'upload-maxfilesize',
00864                 $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['file'] )
00865             )->parse() .
00866                 $this->msg( 'word-separator' )->escaped() .
00867                 $this->msg( 'upload_source_file' )->escaped(),
00868             'checked' => $selectedSourceType == 'file',
00869         );
00870 
00871         if ( $canUploadByUrl ) {
00872             $this->mMaxUploadSize['url'] = UploadBase::getMaxUploadSize( 'url' );
00873             $descriptor['UploadFileURL'] = array(
00874                 'class' => 'UploadSourceField',
00875                 'section' => 'source',
00876                 'id' => 'wpUploadFileURL',
00877                 'radio-id' => 'wpSourceTypeurl',
00878                 'label-message' => 'sourceurl',
00879                 'upload-type' => 'url',
00880                 'radio' => &$radio,
00881                 'help' => $this->msg( 'upload-maxfilesize',
00882                     $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['url'] )
00883                 )->parse() .
00884                     $this->msg( 'word-separator' )->escaped() .
00885                     $this->msg( 'upload_source_url' )->escaped(),
00886                 'checked' => $selectedSourceType == 'url',
00887             );
00888         }
00889         wfRunHooks( 'UploadFormSourceDescriptors', array( &$descriptor, &$radio, $selectedSourceType ) );
00890 
00891         $descriptor['Extensions'] = array(
00892             'type' => 'info',
00893             'section' => 'source',
00894             'default' => $this->getExtensionsMessage(),
00895             'raw' => true,
00896         );
00897 
00898         return $descriptor;
00899     }
00900 
00906     protected function getExtensionsMessage() {
00907         # Print a list of allowed file extensions, if so configured.  We ignore
00908         # MIME type here, it's incomprehensible to most people and too long.
00909         global $wgCheckFileExtensions, $wgStrictFileExtensions,
00910             $wgFileExtensions, $wgFileBlacklist;
00911 
00912         if ( $wgCheckFileExtensions ) {
00913             if ( $wgStrictFileExtensions ) {
00914                 # Everything not permitted is banned
00915                 $extensionsList =
00916                     '<div id="mw-upload-permitted">' .
00917                     $this->msg(
00918                         'upload-permitted',
00919                         $this->getContext()->getLanguage()->commaList( array_unique( $wgFileExtensions ) )
00920                     )->parseAsBlock() .
00921                     "</div>\n";
00922             } else {
00923                 # We have to list both preferred and prohibited
00924                 $extensionsList =
00925                     '<div id="mw-upload-preferred">' .
00926                         $this->msg(
00927                             'upload-preferred',
00928                             $this->getContext()->getLanguage()->commaList( array_unique( $wgFileExtensions ) )
00929                         )->parseAsBlock() .
00930                     "</div>\n" .
00931                     '<div id="mw-upload-prohibited">' .
00932                         $this->msg(
00933                             'upload-prohibited',
00934                             $this->getContext()->getLanguage()->commaList( array_unique( $wgFileBlacklist ) )
00935                         )->parseAsBlock() .
00936                     "</div>\n";
00937             }
00938         } else {
00939             # Everything is permitted.
00940             $extensionsList = '';
00941         }
00942 
00943         return $extensionsList;
00944     }
00945 
00952     protected function getDescriptionSection() {
00953         if ( $this->mSessionKey ) {
00954             $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
00955             try {
00956                 $file = $stash->getFile( $this->mSessionKey );
00957             } catch ( MWException $e ) {
00958                 $file = null;
00959             }
00960             if ( $file ) {
00961                 global $wgContLang;
00962 
00963                 $mto = $file->transform( array( 'width' => 120 ) );
00964                 $this->addHeaderText(
00965                     '<div class="thumb t' . $wgContLang->alignEnd() . '">' .
00966                     Html::element( 'img', array(
00967                         'src' => $mto->getUrl(),
00968                         'class' => 'thumbimage',
00969                     ) ) . '</div>', 'description' );
00970             }
00971         }
00972 
00973         $descriptor = array(
00974             'DestFile' => array(
00975                 'type' => 'text',
00976                 'section' => 'description',
00977                 'id' => 'wpDestFile',
00978                 'label-message' => 'destfilename',
00979                 'size' => 60,
00980                 'default' => $this->mDestFile,
00981                 # @todo FIXME: Hack to work around poor handling of the 'default' option in HTMLForm
00982                 'nodata' => strval( $this->mDestFile ) !== '',
00983             ),
00984             'UploadDescription' => array(
00985                 'type' => 'textarea',
00986                 'section' => 'description',
00987                 'id' => 'wpUploadDescription',
00988                 'label-message' => $this->mForReUpload
00989                     ? 'filereuploadsummary'
00990                     : 'fileuploadsummary',
00991                 'default' => $this->mComment,
00992                 'cols' => $this->getUser()->getIntOption( 'cols' ),
00993                 'rows' => 8,
00994             )
00995         );
00996         if ( $this->mTextAfterSummary ) {
00997             $descriptor['UploadFormTextAfterSummary'] = array(
00998                 'type' => 'info',
00999                 'section' => 'description',
01000                 'default' => $this->mTextAfterSummary,
01001                 'raw' => true,
01002             );
01003         }
01004 
01005         $descriptor += array(
01006             'EditTools' => array(
01007                 'type' => 'edittools',
01008                 'section' => 'description',
01009                 'message' => 'edittools-upload',
01010             )
01011         );
01012 
01013         if ( $this->mForReUpload ) {
01014             $descriptor['DestFile']['readonly'] = true;
01015         } else {
01016             $descriptor['License'] = array(
01017                 'type' => 'select',
01018                 'class' => 'Licenses',
01019                 'section' => 'description',
01020                 'id' => 'wpLicense',
01021                 'label-message' => 'license',
01022             );
01023         }
01024 
01025         global $wgUseCopyrightUpload;
01026         if ( $wgUseCopyrightUpload ) {
01027             $descriptor['UploadCopyStatus'] = array(
01028                 'type' => 'text',
01029                 'section' => 'description',
01030                 'id' => 'wpUploadCopyStatus',
01031                 'label-message' => 'filestatus',
01032             );
01033             $descriptor['UploadSource'] = array(
01034                 'type' => 'text',
01035                 'section' => 'description',
01036                 'id' => 'wpUploadSource',
01037                 'label-message' => 'filesource',
01038             );
01039         }
01040 
01041         return $descriptor;
01042     }
01043 
01050     protected function getOptionsSection() {
01051         $user = $this->getUser();
01052         if ( $user->isLoggedIn() ) {
01053             $descriptor = array(
01054                 'Watchthis' => array(
01055                     'type' => 'check',
01056                     'id' => 'wpWatchthis',
01057                     'label-message' => 'watchthisupload',
01058                     'section' => 'options',
01059                     'default' => $this->mWatch,
01060                 )
01061             );
01062         }
01063         if ( !$this->mHideIgnoreWarning ) {
01064             $descriptor['IgnoreWarning'] = array(
01065                 'type' => 'check',
01066                 'id' => 'wpIgnoreWarning',
01067                 'label-message' => 'ignorewarnings',
01068                 'section' => 'options',
01069             );
01070         }
01071 
01072         $descriptor['DestFileWarningAck'] = array(
01073             'type' => 'hidden',
01074             'id' => 'wpDestFileWarningAck',
01075             'default' => $this->mDestWarningAck ? '1' : '',
01076         );
01077 
01078         if ( $this->mForReUpload ) {
01079             $descriptor['ForReUpload'] = array(
01080                 'type' => 'hidden',
01081                 'id' => 'wpForReUpload',
01082                 'default' => '1',
01083             );
01084         }
01085 
01086         return $descriptor;
01087     }
01088 
01092     public function show() {
01093         $this->addUploadJS();
01094         parent::show();
01095     }
01096 
01100     protected function addUploadJS() {
01101         global $wgUseAjax, $wgAjaxUploadDestCheck, $wgAjaxLicensePreview,
01102             $wgEnableAPI, $wgStrictFileExtensions;
01103 
01104         $useAjaxDestCheck = $wgUseAjax && $wgAjaxUploadDestCheck;
01105         $useAjaxLicensePreview = $wgUseAjax && $wgAjaxLicensePreview && $wgEnableAPI;
01106         $this->mMaxUploadSize['*'] = UploadBase::getMaxUploadSize();
01107 
01108         $scriptVars = array(
01109             'wgAjaxUploadDestCheck' => $useAjaxDestCheck,
01110             'wgAjaxLicensePreview' => $useAjaxLicensePreview,
01111             'wgUploadAutoFill' => !$this->mForReUpload &&
01112                 // If we received mDestFile from the request, don't autofill
01113                 // the wpDestFile textbox
01114                 $this->mDestFile === '',
01115             'wgUploadSourceIds' => $this->mSourceIds,
01116             'wgStrictFileExtensions' => $wgStrictFileExtensions,
01117             'wgCapitalizeUploads' => MWNamespace::isCapitalized( NS_FILE ),
01118             'wgMaxUploadSize' => $this->mMaxUploadSize,
01119         );
01120 
01121         $out = $this->getOutput();
01122         $out->addJsConfigVars( $scriptVars );
01123 
01124         $out->addModules( array(
01125             'mediawiki.action.edit', // For <charinsert> support
01126             'mediawiki.legacy.upload', // Old form stuff...
01127             'mediawiki.special.upload', // Newer extras for thumbnail preview.
01128         ) );
01129     }
01130 
01136     function trySubmit() {
01137         return false;
01138     }
01139 }
01140 
01144 class UploadSourceField extends HTMLTextField {
01145 
01150     function getLabelHtml( $cellAttributes = array() ) {
01151         $id = $this->mParams['id'];
01152         $label = Html::rawElement( 'label', array( 'for' => $id ), $this->mLabel );
01153 
01154         if ( !empty( $this->mParams['radio'] ) ) {
01155             if ( isset( $this->mParams['radio-id'] ) ) {
01156                 $radioId = $this->mParams['radio-id'];
01157             } else {
01158                 // Old way. For the benefit of extensions that do not define
01159                 // the 'radio-id' key.
01160                 $radioId = 'wpSourceType' . $this->mParams['upload-type'];
01161             }
01162 
01163             $attribs = array(
01164                 'name' => 'wpSourceType',
01165                 'type' => 'radio',
01166                 'id' => $radioId,
01167                 'value' => $this->mParams['upload-type'],
01168             );
01169 
01170             if ( !empty( $this->mParams['checked'] ) ) {
01171                 $attribs['checked'] = 'checked';
01172             }
01173 
01174             $label .= Html::element( 'input', $attribs );
01175         }
01176 
01177         return Html::rawElement( 'td', array( 'class' => 'mw-label' ) + $cellAttributes, $label );
01178     }
01179 
01183     function getSize() {
01184         return isset( $this->mParams['size'] )
01185             ? $this->mParams['size']
01186             : 60;
01187     }
01188 }