MediaWiki  REL1_24
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 
00147     public function execute( $par ) {
00148         $this->setHeaders();
00149         $this->outputHeader();
00150 
00151         # Check uploading enabled
00152         if ( !UploadBase::isEnabled() ) {
00153             throw new ErrorPageError( 'uploaddisabled', 'uploaddisabledtext' );
00154         }
00155 
00156         # Check permissions
00157         $user = $this->getUser();
00158         $permissionRequired = UploadBase::isAllowed( $user );
00159         if ( $permissionRequired !== true ) {
00160             throw new PermissionsError( $permissionRequired );
00161         }
00162 
00163         # Check blocks
00164         if ( $user->isBlocked() ) {
00165             throw new UserBlockedError( $user->getBlock() );
00166         }
00167 
00168         # Check whether we actually want to allow changing stuff
00169         $this->checkReadOnly();
00170 
00171         $this->loadRequest();
00172 
00173         # Unsave the temporary file in case this was a cancelled upload
00174         if ( $this->mCancelUpload ) {
00175             if ( !$this->unsaveUploadedFile() ) {
00176                 # Something went wrong, so unsaveUploadedFile showed a warning
00177                 return;
00178             }
00179         }
00180 
00181         # Process upload or show a form
00182         if (
00183             $this->mTokenOk && !$this->mCancelUpload &&
00184             ( $this->mUpload && $this->mUploadClicked )
00185         ) {
00186             $this->processUpload();
00187         } else {
00188             # Backwards compatibility hook
00189             if ( !wfRunHooks( 'UploadForm:initial', array( &$this ) ) ) {
00190                 wfDebug( "Hook 'UploadForm:initial' broke output of the upload form\n" );
00191 
00192                 return;
00193             }
00194             $this->showUploadForm( $this->getUploadForm() );
00195         }
00196 
00197         # Cleanup
00198         if ( $this->mUpload ) {
00199             $this->mUpload->cleanupTempFile();
00200         }
00201     }
00202 
00208     protected function showUploadForm( $form ) {
00209         # Add links if file was previously deleted
00210         if ( $this->mDesiredDestName ) {
00211             $this->showViewDeletedLinks();
00212         }
00213 
00214         if ( $form instanceof HTMLForm ) {
00215             $form->show();
00216         } else {
00217             $this->getOutput()->addHTML( $form );
00218         }
00219     }
00220 
00229     protected function getUploadForm( $message = '', $sessionKey = '', $hideIgnoreWarning = false ) {
00230         # Initialize form
00231         $context = new DerivativeContext( $this->getContext() );
00232         $context->setTitle( $this->getPageTitle() ); // Remove subpage
00233         $form = new UploadForm( array(
00234             'watch' => $this->getWatchCheck(),
00235             'forreupload' => $this->mForReUpload,
00236             'sessionkey' => $sessionKey,
00237             'hideignorewarning' => $hideIgnoreWarning,
00238             'destwarningack' => (bool)$this->mDestWarningAck,
00239 
00240             'description' => $this->mComment,
00241             'texttop' => $this->uploadFormTextTop,
00242             'textaftersummary' => $this->uploadFormTextAfterSummary,
00243             'destfile' => $this->mDesiredDestName,
00244         ), $context );
00245 
00246         # Check the token, but only if necessary
00247         if (
00248             !$this->mTokenOk && !$this->mCancelUpload &&
00249             ( $this->mUpload && $this->mUploadClicked )
00250         ) {
00251             $form->addPreText( $this->msg( 'session_fail_preview' )->parse() );
00252         }
00253 
00254         # Give a notice if the user is uploading a file that has been deleted or moved
00255         # Note that this is independent from the message 'filewasdeleted' that requires JS
00256         $desiredTitleObj = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName );
00257         $delNotice = ''; // empty by default
00258         if ( $desiredTitleObj instanceof Title && !$desiredTitleObj->exists() ) {
00259             LogEventsList::showLogExtract( $delNotice, array( 'delete', 'move' ),
00260                 $desiredTitleObj,
00261                 '', array( 'lim' => 10,
00262                     'conds' => array( "log_action != 'revision'" ),
00263                     'showIfEmpty' => false,
00264                     'msgKey' => array( 'upload-recreate-warning' ) )
00265             );
00266         }
00267         $form->addPreText( $delNotice );
00268 
00269         # Add text to form
00270         $form->addPreText( '<div id="uploadtext">' .
00271             $this->msg( 'uploadtext', array( $this->mDesiredDestName ) )->parseAsBlock() .
00272             '</div>' );
00273         # Add upload error message
00274         $form->addPreText( $message );
00275 
00276         # Add footer to form
00277         $uploadFooter = $this->msg( 'uploadfooter' );
00278         if ( !$uploadFooter->isDisabled() ) {
00279             $form->addPostText( '<div id="mw-upload-footer-message">'
00280                 . $uploadFooter->parseAsBlock() . "</div>\n" );
00281         }
00282 
00283         return $form;
00284     }
00285 
00289     protected function showViewDeletedLinks() {
00290         $title = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName );
00291         $user = $this->getUser();
00292         // Show a subtitle link to deleted revisions (to sysops et al only)
00293         if ( $title instanceof Title ) {
00294             $count = $title->isDeleted();
00295             if ( $count > 0 && $user->isAllowed( 'deletedhistory' ) ) {
00296                 $restorelink = Linker::linkKnown(
00297                     SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedText() ),
00298                     $this->msg( 'restorelink' )->numParams( $count )->escaped()
00299                 );
00300                 $link = $this->msg( $user->isAllowed( 'delete' ) ? 'thisisdeleted' : 'viewdeleted' )
00301                     ->rawParams( $restorelink )->parseAsBlock();
00302                 $this->getOutput()->addHTML( "<div id=\"contentSub2\">{$link}</div>" );
00303             }
00304         }
00305     }
00306 
00318     protected function showRecoverableUploadError( $message ) {
00319         $sessionKey = $this->mUpload->stashSession();
00320         $message = '<h2>' . $this->msg( 'uploaderror' )->escaped() . "</h2>\n" .
00321             '<div class="error">' . $message . "</div>\n";
00322 
00323         $form = $this->getUploadForm( $message, $sessionKey );
00324         $form->setSubmitText( $this->msg( 'upload-tryagain' )->escaped() );
00325         $this->showUploadForm( $form );
00326     }
00327 
00336     protected function showUploadWarning( $warnings ) {
00337         # If there are no warnings, or warnings we can ignore, return early.
00338         # mDestWarningAck is set when some javascript has shown the warning
00339         # to the user. mForReUpload is set when the user clicks the "upload a
00340         # new version" link.
00341         if ( !$warnings || ( count( $warnings ) == 1
00342             && isset( $warnings['exists'] )
00343             && ( $this->mDestWarningAck || $this->mForReUpload ) )
00344         ) {
00345             return false;
00346         }
00347 
00348         $sessionKey = $this->mUpload->stashSession();
00349 
00350         $warningHtml = '<h2>' . $this->msg( 'uploadwarning' )->escaped() . "</h2>\n"
00351             . '<ul class="warning">';
00352         foreach ( $warnings as $warning => $args ) {
00353             if ( $warning == 'badfilename' ) {
00354                 $this->mDesiredDestName = Title::makeTitle( NS_FILE, $args )->getText();
00355             }
00356             if ( $warning == 'exists' ) {
00357                 $msg = "\t<li>" . self::getExistsWarning( $args ) . "</li>\n";
00358             } elseif ( $warning == 'duplicate' ) {
00359                 $msg = $this->getDupeWarning( $args );
00360             } elseif ( $warning == 'duplicate-archive' ) {
00361                 if ( $args === '' ) {
00362                     $msg = "\t<li>" . $this->msg( 'file-deleted-duplicate-notitle' )->parse()
00363                         . "</li>\n";
00364                 } else {
00365                     $msg = "\t<li>" . $this->msg( 'file-deleted-duplicate',
00366                             Title::makeTitle( NS_FILE, $args )->getPrefixedText() )->parse()
00367                         . "</li>\n";
00368                 }
00369             } else {
00370                 if ( $args === true ) {
00371                     $args = array();
00372                 } elseif ( !is_array( $args ) ) {
00373                     $args = array( $args );
00374                 }
00375                 $msg = "\t<li>" . $this->msg( $warning, $args )->parse() . "</li>\n";
00376             }
00377             $warningHtml .= $msg;
00378         }
00379         $warningHtml .= "</ul>\n";
00380         $warningHtml .= $this->msg( 'uploadwarning-text' )->parseAsBlock();
00381 
00382         $form = $this->getUploadForm( $warningHtml, $sessionKey, /* $hideIgnoreWarning */ true );
00383         $form->setSubmitText( $this->msg( 'upload-tryagain' )->text() );
00384         $form->addButton( 'wpUploadIgnoreWarning', $this->msg( 'ignorewarning' )->text() );
00385         $form->addButton( 'wpCancelUpload', $this->msg( 'reuploaddesc' )->text() );
00386 
00387         $this->showUploadForm( $form );
00388 
00389         # Indicate that we showed a form
00390         return true;
00391     }
00392 
00398     protected function showUploadError( $message ) {
00399         $message = '<h2>' . $this->msg( 'uploadwarning' )->escaped() . "</h2>\n" .
00400             '<div class="error">' . $message . "</div>\n";
00401         $this->showUploadForm( $this->getUploadForm( $message ) );
00402     }
00403 
00408     protected function processUpload() {
00409         // Fetch the file if required
00410         $status = $this->mUpload->fetchFile();
00411         if ( !$status->isOK() ) {
00412             $this->showUploadError( $this->getOutput()->parse( $status->getWikiText() ) );
00413 
00414             return;
00415         }
00416 
00417         if ( !wfRunHooks( 'UploadForm:BeforeProcessing', array( &$this ) ) ) {
00418             wfDebug( "Hook 'UploadForm:BeforeProcessing' broke processing the file.\n" );
00419             // This code path is deprecated. If you want to break upload processing
00420             // do so by hooking into the appropriate hooks in UploadBase::verifyUpload
00421             // and UploadBase::verifyFile.
00422             // If you use this hook to break uploading, the user will be returned
00423             // an empty form with no error message whatsoever.
00424             return;
00425         }
00426 
00427         // Upload verification
00428         $details = $this->mUpload->verifyUpload();
00429         if ( $details['status'] != UploadBase::OK ) {
00430             $this->processVerificationError( $details );
00431 
00432             return;
00433         }
00434 
00435         // Verify permissions for this title
00436         $permErrors = $this->mUpload->verifyTitlePermissions( $this->getUser() );
00437         if ( $permErrors !== true ) {
00438             $code = array_shift( $permErrors[0] );
00439             $this->showRecoverableUploadError( $this->msg( $code, $permErrors[0] )->parse() );
00440 
00441             return;
00442         }
00443 
00444         $this->mLocalFile = $this->mUpload->getLocalFile();
00445 
00446         // Check warnings if necessary
00447         if ( !$this->mIgnoreWarning ) {
00448             $warnings = $this->mUpload->checkWarnings();
00449             if ( $this->showUploadWarning( $warnings ) ) {
00450                 return;
00451             }
00452         }
00453 
00454         // Get the page text if this is not a reupload
00455         if ( !$this->mForReUpload ) {
00456             $pageText = self::getInitialPageText( $this->mComment, $this->mLicense,
00457                 $this->mCopyrightStatus, $this->mCopyrightSource );
00458         } else {
00459             $pageText = false;
00460         }
00461 
00462         $status = $this->mUpload->performUpload(
00463             $this->mComment,
00464             $pageText,
00465             $this->mWatchthis,
00466             $this->getUser()
00467         );
00468 
00469         if ( !$status->isGood() ) {
00470             $this->showUploadError( $this->getOutput()->parse( $status->getWikiText() ) );
00471 
00472             return;
00473         }
00474 
00475         // Success, redirect to description page
00476         $this->mUploadSuccessful = true;
00477         wfRunHooks( 'SpecialUploadComplete', array( &$this ) );
00478         $this->getOutput()->redirect( $this->mLocalFile->getTitle()->getFullURL() );
00479     }
00480 
00490     public static function getInitialPageText( $comment = '', $license = '',
00491         $copyStatus = '', $source = ''
00492     ) {
00493         global $wgUseCopyrightUpload, $wgForceUIMsgAsContentMsg;
00494 
00495         $msg = array();
00496         /* These messages are transcluded into the actual text of the description page.
00497          * Thus, forcing them as content messages makes the upload to produce an int: template
00498          * instead of hardcoding it there in the uploader language.
00499          */
00500         foreach ( array( 'license-header', 'filedesc', 'filestatus', 'filesource' ) as $msgName ) {
00501             if ( in_array( $msgName, (array)$wgForceUIMsgAsContentMsg ) ) {
00502                 $msg[$msgName] = "{{int:$msgName}}";
00503             } else {
00504                 $msg[$msgName] = wfMessage( $msgName )->inContentLanguage()->text();
00505             }
00506         }
00507 
00508         if ( $wgUseCopyrightUpload ) {
00509             $licensetxt = '';
00510             if ( $license != '' ) {
00511                 $licensetxt = '== ' . $msg['license-header'] . " ==\n" . '{{' . $license . '}}' . "\n";
00512             }
00513             $pageText = '== ' . $msg['filedesc'] . " ==\n" . $comment . "\n" .
00514                 '== ' . $msg['filestatus'] . " ==\n" . $copyStatus . "\n" .
00515                 "$licensetxt" .
00516                 '== ' . $msg['filesource'] . " ==\n" . $source;
00517         } else {
00518             if ( $license != '' ) {
00519                 $filedesc = $comment == '' ? '' : '== ' . $msg['filedesc'] . " ==\n" . $comment . "\n";
00520                     $pageText = $filedesc .
00521                     '== ' . $msg['license-header'] . " ==\n" . '{{' . $license . '}}' . "\n";
00522             } else {
00523                 $pageText = $comment;
00524             }
00525         }
00526 
00527         return $pageText;
00528     }
00529 
00542     protected function getWatchCheck() {
00543         if ( $this->getUser()->getOption( 'watchdefault' ) ) {
00544             // Watch all edits!
00545             return true;
00546         }
00547 
00548         $desiredTitleObj = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName );
00549         if ( $desiredTitleObj instanceof Title && $this->getUser()->isWatched( $desiredTitleObj ) ) {
00550             // Already watched, don't change that
00551             return true;
00552         }
00553 
00554         $local = wfLocalFile( $this->mDesiredDestName );
00555         if ( $local && $local->exists() ) {
00556             // We're uploading a new version of an existing file.
00557             // No creation, so don't watch it if we're not already.
00558             return false;
00559         } else {
00560             // New page should get watched if that's our option.
00561             return $this->getUser()->getOption( 'watchcreations' );
00562         }
00563     }
00564 
00571     protected function processVerificationError( $details ) {
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( $this->getConfig()->get( 'FileExtensions' ) );
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( false, $this->getContext() );
00728         $gallery->setShowBytes( false );
00729         foreach ( $dupes as $file ) {
00730             $gallery->add( $file->getTitle() );
00731         }
00732 
00733         return '<li>' .
00734             wfMessage( 'file-exists-duplicate' )->numParams( count( $dupes ) )->parse() .
00735             $gallery->toHtml() . "</li>\n";
00736     }
00737 
00738     protected function getGroupName() {
00739         return 'media';
00740     }
00741 
00749     static public function rotationEnabled() {
00750         $bitmapHandler = new BitmapHandler();
00751         return $bitmapHandler->autoRotateEnabled();
00752     }
00753 }
00754 
00758 class UploadForm extends HTMLForm {
00759     protected $mWatch;
00760     protected $mForReUpload;
00761     protected $mSessionKey;
00762     protected $mHideIgnoreWarning;
00763     protected $mDestWarningAck;
00764     protected $mDestFile;
00765 
00766     protected $mComment;
00767     protected $mTextTop;
00768     protected $mTextAfterSummary;
00769 
00770     protected $mSourceIds;
00771 
00772     protected $mMaxFileSize = array();
00773 
00774     protected $mMaxUploadSize = array();
00775 
00776     public function __construct( array $options = array(), IContextSource $context = null ) {
00777         $this->mWatch = !empty( $options['watch'] );
00778         $this->mForReUpload = !empty( $options['forreupload'] );
00779         $this->mSessionKey = isset( $options['sessionkey'] ) ? $options['sessionkey'] : '';
00780         $this->mHideIgnoreWarning = !empty( $options['hideignorewarning'] );
00781         $this->mDestWarningAck = !empty( $options['destwarningack'] );
00782         $this->mDestFile = isset( $options['destfile'] ) ? $options['destfile'] : '';
00783 
00784         $this->mComment = isset( $options['description'] ) ?
00785             $options['description'] : '';
00786 
00787         $this->mTextTop = isset( $options['texttop'] )
00788             ? $options['texttop'] : '';
00789 
00790         $this->mTextAfterSummary = isset( $options['textaftersummary'] )
00791             ? $options['textaftersummary'] : '';
00792 
00793         $sourceDescriptor = $this->getSourceSection();
00794         $descriptor = $sourceDescriptor
00795             + $this->getDescriptionSection()
00796             + $this->getOptionsSection();
00797 
00798         wfRunHooks( 'UploadFormInitDescriptor', array( &$descriptor ) );
00799         parent::__construct( $descriptor, $context, 'upload' );
00800 
00801         # Add a link to edit MediaWik:Licenses
00802         if ( $this->getUser()->isAllowed( 'editinterface' ) ) {
00803             $licensesLink = Linker::link(
00804                 Title::makeTitle( NS_MEDIAWIKI, 'Licenses' ),
00805                 $this->msg( 'licenses-edit' )->escaped(),
00806                 array(),
00807                 array( 'action' => 'edit' )
00808             );
00809             $editLicenses = '<p class="mw-upload-editlicenses">' . $licensesLink . '</p>';
00810             $this->addFooterText( $editLicenses, 'description' );
00811         }
00812 
00813         # Set some form properties
00814         $this->setSubmitText( $this->msg( 'uploadbtn' )->text() );
00815         $this->setSubmitName( 'wpUpload' );
00816         # Used message keys: 'accesskey-upload', 'tooltip-upload'
00817         $this->setSubmitTooltip( 'upload' );
00818         $this->setId( 'mw-upload-form' );
00819 
00820         # Build a list of IDs for javascript insertion
00821         $this->mSourceIds = array();
00822         foreach ( $sourceDescriptor as $field ) {
00823             if ( !empty( $field['id'] ) ) {
00824                 $this->mSourceIds[] = $field['id'];
00825             }
00826         }
00827     }
00828 
00835     protected function getSourceSection() {
00836         if ( $this->mSessionKey ) {
00837             return array(
00838                 'SessionKey' => array(
00839                     'type' => 'hidden',
00840                     'default' => $this->mSessionKey,
00841                 ),
00842                 'SourceType' => array(
00843                     'type' => 'hidden',
00844                     'default' => 'Stash',
00845                 ),
00846             );
00847         }
00848 
00849         $canUploadByUrl = UploadFromUrl::isEnabled()
00850             && ( UploadFromUrl::isAllowed( $this->getUser() ) === true )
00851             && $this->getConfig()->get( 'CopyUploadsFromSpecialUpload' );
00852         $radio = $canUploadByUrl;
00853         $selectedSourceType = strtolower( $this->getRequest()->getText( 'wpSourceType', 'File' ) );
00854 
00855         $descriptor = array();
00856         if ( $this->mTextTop ) {
00857             $descriptor['UploadFormTextTop'] = array(
00858                 'type' => 'info',
00859                 'section' => 'source',
00860                 'default' => $this->mTextTop,
00861                 'raw' => true,
00862             );
00863         }
00864 
00865         $this->mMaxUploadSize['file'] = UploadBase::getMaxUploadSize( 'file' );
00866         # Limit to upload_max_filesize unless we are running under HipHop and
00867         # that setting doesn't exist
00868         if ( !wfIsHHVM() ) {
00869             $this->mMaxUploadSize['file'] = min( $this->mMaxUploadSize['file'],
00870                 wfShorthandToInteger( ini_get( 'upload_max_filesize' ) ),
00871                 wfShorthandToInteger( ini_get( 'post_max_size' ) )
00872             );
00873         }
00874 
00875         $descriptor['UploadFile'] = array(
00876             'class' => 'UploadSourceField',
00877             'section' => 'source',
00878             'type' => 'file',
00879             'id' => 'wpUploadFile',
00880             'radio-id' => 'wpSourceTypeFile',
00881             'label-message' => 'sourcefilename',
00882             'upload-type' => 'File',
00883             'radio' => &$radio,
00884             'help' => $this->msg( 'upload-maxfilesize',
00885                 $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['file'] )
00886             )->parse() .
00887                 $this->msg( 'word-separator' )->escaped() .
00888                 $this->msg( 'upload_source_file' )->escaped(),
00889             'checked' => $selectedSourceType == 'file',
00890         );
00891 
00892         if ( $canUploadByUrl ) {
00893             $this->mMaxUploadSize['url'] = UploadBase::getMaxUploadSize( 'url' );
00894             $descriptor['UploadFileURL'] = array(
00895                 'class' => 'UploadSourceField',
00896                 'section' => 'source',
00897                 'id' => 'wpUploadFileURL',
00898                 'radio-id' => 'wpSourceTypeurl',
00899                 'label-message' => 'sourceurl',
00900                 'upload-type' => 'url',
00901                 'radio' => &$radio,
00902                 'help' => $this->msg( 'upload-maxfilesize',
00903                     $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['url'] )
00904                 )->parse() .
00905                     $this->msg( 'word-separator' )->escaped() .
00906                     $this->msg( 'upload_source_url' )->escaped(),
00907                 'checked' => $selectedSourceType == 'url',
00908             );
00909         }
00910         wfRunHooks( 'UploadFormSourceDescriptors', array( &$descriptor, &$radio, $selectedSourceType ) );
00911 
00912         $descriptor['Extensions'] = array(
00913             'type' => 'info',
00914             'section' => 'source',
00915             'default' => $this->getExtensionsMessage(),
00916             'raw' => true,
00917         );
00918 
00919         return $descriptor;
00920     }
00921 
00927     protected function getExtensionsMessage() {
00928         # Print a list of allowed file extensions, if so configured.  We ignore
00929         # MIME type here, it's incomprehensible to most people and too long.
00930         $config = $this->getConfig();
00931 
00932         if ( $config->get( 'CheckFileExtensions' ) ) {
00933             if ( $config->get( 'StrictFileExtensions' ) ) {
00934                 # Everything not permitted is banned
00935                 $extensionsList =
00936                     '<div id="mw-upload-permitted">' .
00937                     $this->msg(
00938                         'upload-permitted',
00939                         $this->getContext()->getLanguage()->commaList(
00940                             array_unique( $config->get( 'FileExtensions' ) )
00941                         )
00942                     )->parseAsBlock() .
00943                     "</div>\n";
00944             } else {
00945                 # We have to list both preferred and prohibited
00946                 $extensionsList =
00947                     '<div id="mw-upload-preferred">' .
00948                         $this->msg(
00949                             'upload-preferred',
00950                             $this->getContext()->getLanguage()->commaList(
00951                                 array_unique( $config->get( 'FileExtensions' ) )
00952                             )
00953                         )->parseAsBlock() .
00954                     "</div>\n" .
00955                     '<div id="mw-upload-prohibited">' .
00956                         $this->msg(
00957                             'upload-prohibited',
00958                             $this->getContext()->getLanguage()->commaList(
00959                                 array_unique( $config->get( 'FileBlacklist' ) )
00960                             )
00961                         )->parseAsBlock() .
00962                     "</div>\n";
00963             }
00964         } else {
00965             # Everything is permitted.
00966             $extensionsList = '';
00967         }
00968 
00969         return $extensionsList;
00970     }
00971 
00978     protected function getDescriptionSection() {
00979         $config = $this->getConfig();
00980         if ( $this->mSessionKey ) {
00981             $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
00982             try {
00983                 $file = $stash->getFile( $this->mSessionKey );
00984             } catch ( MWException $e ) {
00985                 $file = null;
00986             }
00987             if ( $file ) {
00988                 global $wgContLang;
00989 
00990                 $mto = $file->transform( array( 'width' => 120 ) );
00991                 $this->addHeaderText(
00992                     '<div class="thumb t' . $wgContLang->alignEnd() . '">' .
00993                     Html::element( 'img', array(
00994                         'src' => $mto->getUrl(),
00995                         'class' => 'thumbimage',
00996                     ) ) . '</div>', 'description' );
00997             }
00998         }
00999 
01000         $descriptor = array(
01001             'DestFile' => array(
01002                 'type' => 'text',
01003                 'section' => 'description',
01004                 'id' => 'wpDestFile',
01005                 'label-message' => 'destfilename',
01006                 'size' => 60,
01007                 'default' => $this->mDestFile,
01008                 # @todo FIXME: Hack to work around poor handling of the 'default' option in HTMLForm
01009                 'nodata' => strval( $this->mDestFile ) !== '',
01010             ),
01011             'UploadDescription' => array(
01012                 'type' => 'textarea',
01013                 'section' => 'description',
01014                 'id' => 'wpUploadDescription',
01015                 'label-message' => $this->mForReUpload
01016                     ? 'filereuploadsummary'
01017                     : 'fileuploadsummary',
01018                 'default' => $this->mComment,
01019                 'cols' => $this->getUser()->getIntOption( 'cols' ),
01020                 'rows' => 8,
01021             )
01022         );
01023         if ( $this->mTextAfterSummary ) {
01024             $descriptor['UploadFormTextAfterSummary'] = array(
01025                 'type' => 'info',
01026                 'section' => 'description',
01027                 'default' => $this->mTextAfterSummary,
01028                 'raw' => true,
01029             );
01030         }
01031 
01032         $descriptor += array(
01033             'EditTools' => array(
01034                 'type' => 'edittools',
01035                 'section' => 'description',
01036                 'message' => 'edittools-upload',
01037             )
01038         );
01039 
01040         if ( $this->mForReUpload ) {
01041             $descriptor['DestFile']['readonly'] = true;
01042         } else {
01043             $descriptor['License'] = array(
01044                 'type' => 'select',
01045                 'class' => 'Licenses',
01046                 'section' => 'description',
01047                 'id' => 'wpLicense',
01048                 'label-message' => 'license',
01049             );
01050         }
01051 
01052         if ( $config->get( 'UseCopyrightUpload' ) ) {
01053             $descriptor['UploadCopyStatus'] = array(
01054                 'type' => 'text',
01055                 'section' => 'description',
01056                 'id' => 'wpUploadCopyStatus',
01057                 'label-message' => 'filestatus',
01058             );
01059             $descriptor['UploadSource'] = array(
01060                 'type' => 'text',
01061                 'section' => 'description',
01062                 'id' => 'wpUploadSource',
01063                 'label-message' => 'filesource',
01064             );
01065         }
01066 
01067         return $descriptor;
01068     }
01069 
01076     protected function getOptionsSection() {
01077         $user = $this->getUser();
01078         if ( $user->isLoggedIn() ) {
01079             $descriptor = array(
01080                 'Watchthis' => array(
01081                     'type' => 'check',
01082                     'id' => 'wpWatchthis',
01083                     'label-message' => 'watchthisupload',
01084                     'section' => 'options',
01085                     'default' => $this->mWatch,
01086                 )
01087             );
01088         }
01089         if ( !$this->mHideIgnoreWarning ) {
01090             $descriptor['IgnoreWarning'] = array(
01091                 'type' => 'check',
01092                 'id' => 'wpIgnoreWarning',
01093                 'label-message' => 'ignorewarnings',
01094                 'section' => 'options',
01095             );
01096         }
01097 
01098         $descriptor['DestFileWarningAck'] = array(
01099             'type' => 'hidden',
01100             'id' => 'wpDestFileWarningAck',
01101             'default' => $this->mDestWarningAck ? '1' : '',
01102         );
01103 
01104         if ( $this->mForReUpload ) {
01105             $descriptor['ForReUpload'] = array(
01106                 'type' => 'hidden',
01107                 'id' => 'wpForReUpload',
01108                 'default' => '1',
01109             );
01110         }
01111 
01112         return $descriptor;
01113     }
01114 
01118     public function show() {
01119         $this->addUploadJS();
01120         parent::show();
01121     }
01122 
01126     protected function addUploadJS() {
01127         $config = $this->getConfig();
01128 
01129         $useAjaxDestCheck = $config->get( 'UseAjax' ) && $config->get( 'AjaxUploadDestCheck' );
01130         $useAjaxLicensePreview = $config->get( 'UseAjax' ) &&
01131             $config->get( 'AjaxLicensePreview' ) && $config->get( 'EnableAPI' );
01132         $this->mMaxUploadSize['*'] = UploadBase::getMaxUploadSize();
01133 
01134         $scriptVars = array(
01135             'wgAjaxUploadDestCheck' => $useAjaxDestCheck,
01136             'wgAjaxLicensePreview' => $useAjaxLicensePreview,
01137             'wgUploadAutoFill' => !$this->mForReUpload &&
01138                 // If we received mDestFile from the request, don't autofill
01139                 // the wpDestFile textbox
01140                 $this->mDestFile === '',
01141             'wgUploadSourceIds' => $this->mSourceIds,
01142             'wgStrictFileExtensions' => $config->get( 'StrictFileExtensions' ),
01143             'wgCapitalizeUploads' => MWNamespace::isCapitalized( NS_FILE ),
01144             'wgMaxUploadSize' => $this->mMaxUploadSize,
01145         );
01146 
01147         $out = $this->getOutput();
01148         $out->addJsConfigVars( $scriptVars );
01149 
01150         $out->addModules( array(
01151             'mediawiki.action.edit', // For <charinsert> support
01152             'mediawiki.special.upload', // Extras for thumbnail and license preview.
01153         ) );
01154     }
01155 
01161     function trySubmit() {
01162         return false;
01163     }
01164 }
01165 
01169 class UploadSourceField extends HTMLTextField {
01170 
01175     function getLabelHtml( $cellAttributes = array() ) {
01176         $id = $this->mParams['id'];
01177         $label = Html::rawElement( 'label', array( 'for' => $id ), $this->mLabel );
01178 
01179         if ( !empty( $this->mParams['radio'] ) ) {
01180             if ( isset( $this->mParams['radio-id'] ) ) {
01181                 $radioId = $this->mParams['radio-id'];
01182             } else {
01183                 // Old way. For the benefit of extensions that do not define
01184                 // the 'radio-id' key.
01185                 $radioId = 'wpSourceType' . $this->mParams['upload-type'];
01186             }
01187 
01188             $attribs = array(
01189                 'name' => 'wpSourceType',
01190                 'type' => 'radio',
01191                 'id' => $radioId,
01192                 'value' => $this->mParams['upload-type'],
01193             );
01194 
01195             if ( !empty( $this->mParams['checked'] ) ) {
01196                 $attribs['checked'] = 'checked';
01197             }
01198 
01199             $label .= Html::element( 'input', $attribs );
01200         }
01201 
01202         return Html::rawElement( 'td', array( 'class' => 'mw-label' ) + $cellAttributes, $label );
01203     }
01204 
01208     function getSize() {
01209         return isset( $this->mParams['size'] )
01210             ? $this->mParams['size']
01211             : 60;
01212     }
01213 }