X-Git-Url: https://scripts.mit.edu/gitweb/autoinstalls/mediawiki.git/blobdiff_plain/96940cda03e7d46e1ce0b13d087122e157391a04..HEAD:/includes/specials/SpecialUpload.php diff --git a/includes/specials/SpecialUpload.php b/includes/specials/SpecialUpload.php index 68ee8efc..024034a6 100644 --- a/includes/specials/SpecialUpload.php +++ b/includes/specials/SpecialUpload.php @@ -1,103 +1,134 @@ loadRequest( is_null( $request ) ? $wgRequest : $request ); + public function doesWrites() { + return true; } /** Misc variables **/ - public $mRequest; // The WebRequest or FauxRequest this form is supposed to handle + + /** @var WebRequest|FauxRequest The request this form is supposed to handle */ + public $mRequest; public $mSourceType; + + /** @var UploadBase */ public $mUpload; + + /** @var LocalFile */ public $mLocalFile; public $mUploadClicked; /** User input variables from the "description" section **/ - public $mDesiredDestName; // The requested target file name + + /** @var string The requested target file name */ + public $mDesiredDestName; public $mComment; public $mLicense; /** User input variables from the root section **/ + public $mIgnoreWarning; - public $mWatchThis; + public $mWatchthis; public $mCopyrightStatus; public $mCopyrightSource; /** Hidden variables **/ + public $mDestWarningAck; - public $mForReUpload; // The user followed an "overwrite this file" link - public $mCancelUpload; // The user clicked "Cancel and return to upload form" button + + /** @var bool The user followed an "overwrite this file" link */ + public $mForReUpload; + + /** @var bool The user clicked "Cancel and return to upload form" button */ + public $mCancelUpload; public $mTokenOk; - public $mUploadSuccessful = false; // Subclasses can use this to determine whether a file was uploaded + + /** @var bool Subclasses can use this to determine whether a file was uploaded */ + public $mUploadSuccessful = false; /** Text injection points for hooks not using HTMLForm **/ public $uploadFormTextTop; public $uploadFormTextAfterSummary; - /** * Initialize instance variables from request and create an Upload handler - * - * @param WebRequest $request The request to extract variables from */ - protected function loadRequest( $request ) { - global $wgUser; - - $this->mRequest = $request; - $this->mSourceType = $request->getVal( 'wpSourceType', 'file' ); - $this->mUpload = UploadBase::createFromRequest( $request ); - $this->mUploadClicked = $request->wasPosted() - && ( $request->getCheck( 'wpUpload' ) + protected function loadRequest() { + $this->mRequest = $request = $this->getRequest(); + $this->mSourceType = $request->getVal( 'wpSourceType', 'file' ); + $this->mUpload = UploadBase::createFromRequest( $request ); + $this->mUploadClicked = $request->wasPosted() + && ( $request->getCheck( 'wpUpload' ) || $request->getCheck( 'wpUploadIgnoreWarning' ) ); // Guess the desired name from the filename if not provided - $this->mDesiredDestName = $request->getText( 'wpDestFile' ); - if( !$this->mDesiredDestName && $request->getFileName( 'wpUploadFile' ) !== null ) + $this->mDesiredDestName = $request->getText( 'wpDestFile' ); + if ( !$this->mDesiredDestName && $request->getFileName( 'wpUploadFile' ) !== null ) { $this->mDesiredDestName = $request->getFileName( 'wpUploadFile' ); - $this->mComment = $request->getText( 'wpUploadDescription' ); - $this->mLicense = $request->getText( 'wpLicense' ); - + } + $this->mLicense = $request->getText( 'wpLicense' ); - $this->mDestWarningAck = $request->getText( 'wpDestFileWarningAck' ); - $this->mIgnoreWarning = $request->getCheck( 'wpIgnoreWarning' ) + $this->mDestWarningAck = $request->getText( 'wpDestFileWarningAck' ); + $this->mIgnoreWarning = $request->getCheck( 'wpIgnoreWarning' ) || $request->getCheck( 'wpUploadIgnoreWarning' ); - $this->mWatchthis = $request->getBool( 'wpWatchthis' ) && $wgUser->isLoggedIn(); - $this->mCopyrightStatus = $request->getText( 'wpUploadCopyStatus' ); - $this->mCopyrightSource = $request->getText( 'wpUploadSource' ); + $this->mWatchthis = $request->getBool( 'wpWatchthis' ) && $this->getUser()->isLoggedIn(); + $this->mCopyrightStatus = $request->getText( 'wpUploadCopyStatus' ); + $this->mCopyrightSource = $request->getText( 'wpUploadSource' ); + $this->mForReUpload = $request->getBool( 'wpForReUpload' ); // updating a file - $this->mForReUpload = $request->getBool( 'wpForReUpload' ); // updating a file - $this->mCancelUpload = $request->getCheck( 'wpCancelUpload' ) - || $request->getCheck( 'wpReUpload' ); // b/w compat + $commentDefault = ''; + $commentMsg = wfMessage( 'upload-default-description' )->inContentLanguage(); + if ( !$this->mForReUpload && !$commentMsg->isDisabled() ) { + $commentDefault = $commentMsg->plain(); + } + $this->mComment = $request->getText( 'wpUploadDescription', $commentDefault ); + + $this->mCancelUpload = $request->getCheck( 'wpCancelUpload' ) + || $request->getCheck( 'wpReUpload' ); // b/w compat // If it was posted check for the token (no remote POST'ing with user credentials) $token = $request->getVal( 'wpEditToken' ); - if( $this->mSourceType == 'file' && $token == null ) { - // Skip token check for file uploads as that can't be faked via JS... - // Some client-side tools don't expect to need to send wpEditToken - // with their submissions, as that's new in 1.16. - $this->mTokenOk = true; - } else { - $this->mTokenOk = $wgUser->matchEditToken( $token ); - } - + $this->mTokenOk = $this->getUser()->matchEditToken( $token ); + $this->uploadFormTextTop = ''; $this->uploadFormTextAfterSummary = ''; } @@ -110,95 +141,104 @@ class SpecialUpload extends SpecialPage { * @param User $user * @return bool */ - public function userCanExecute( $user ) { + public function userCanExecute( User $user ) { return UploadBase::isEnabled() && parent::userCanExecute( $user ); } /** * Special page entry point + * @param string $par + * @throws ErrorPageError + * @throws Exception + * @throws FatalError + * @throws MWException + * @throws PermissionsError + * @throws ReadOnlyError + * @throws UserBlockedError */ public function execute( $par ) { - global $wgUser, $wgOut, $wgRequest; + $this->useTransactionalTimeLimit(); $this->setHeaders(); $this->outputHeader(); # Check uploading enabled - if( !UploadBase::isEnabled() ) { - $wgOut->showErrorPage( 'uploaddisabled', 'uploaddisabledtext' ); - return; + if ( !UploadBase::isEnabled() ) { + throw new ErrorPageError( 'uploaddisabled', 'uploaddisabledtext' ); } + $this->addHelpLink( 'Help:Managing files' ); + # Check permissions - global $wgGroupPermissions; - if( !$wgUser->isAllowed( 'upload' ) ) { - if( !$wgUser->isLoggedIn() && ( $wgGroupPermissions['user']['upload'] - || $wgGroupPermissions['autoconfirmed']['upload'] ) ) { - // Custom message if logged-in users without any special rights can upload - $wgOut->showErrorPage( 'uploadnologin', 'uploadnologintext' ); - } else { - $wgOut->permissionRequired( 'upload' ); - } - return; + $user = $this->getUser(); + $permissionRequired = UploadBase::isAllowed( $user ); + if ( $permissionRequired !== true ) { + throw new PermissionsError( $permissionRequired ); } # Check blocks - if( $wgUser->isBlocked() ) { - $wgOut->blockedPage(); - return; + if ( $user->isBlocked() ) { + throw new UserBlockedError( $user->getBlock() ); } - # Check whether we actually want to allow changing stuff - if( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return; + // Global blocks + if ( $user->isBlockedGlobally() ) { + throw new UserBlockedError( $user->getGlobalBlock() ); } + # Check whether we actually want to allow changing stuff + $this->checkReadOnly(); + + $this->loadRequest(); + # Unsave the temporary file in case this was a cancelled upload if ( $this->mCancelUpload ) { - if ( !$this->unsaveUploadedFile() ) + if ( !$this->unsaveUploadedFile() ) { # Something went wrong, so unsaveUploadedFile showed a warning return; + } } # Process upload or show a form - if ( $this->mTokenOk && !$this->mCancelUpload - && ( $this->mUpload && $this->mUploadClicked ) ) { + if ( + $this->mTokenOk && !$this->mCancelUpload && + ( $this->mUpload && $this->mUploadClicked ) + ) { $this->processUpload(); } else { # Backwards compatibility hook - if( !wfRunHooks( 'UploadForm:initial', array( &$this ) ) ) - { - wfDebug( "Hook 'UploadForm:initial' broke output of the upload form" ); + // Avoid PHP 7.1 warning of passing $this by reference + $upload = $this; + if ( !Hooks::run( 'UploadForm:initial', [ &$upload ] ) ) { + wfDebug( "Hook 'UploadForm:initial' broke output of the upload form\n" ); + return; } - $this->showUploadForm( $this->getUploadForm() ); } # Cleanup - if ( $this->mUpload ) + if ( $this->mUpload ) { $this->mUpload->cleanupTempFile(); + } } /** - * Show the main upload form + * Show the main upload form * - * @param mixed $form An HTMLForm instance or HTML string to show + * @param HTMLForm|string $form An HTMLForm instance or HTML string to show */ protected function showUploadForm( $form ) { # Add links if file was previously deleted - if ( !$this->mDesiredDestName ) { + if ( $this->mDesiredDestName ) { $this->showViewDeletedLinks(); } - + if ( $form instanceof HTMLForm ) { $form->show(); } else { - global $wgOut; - $wgOut->addHTML( $form ); + $this->getOutput()->addHTML( $form ); } - } /** @@ -206,76 +246,87 @@ class SpecialUpload extends SpecialPage { * * @param string $message HTML string to add to the form * @param string $sessionKey Session key in case this is a stashed upload + * @param bool $hideIgnoreWarning Whether to hide "ignore warning" check box * @return UploadForm */ protected function getUploadForm( $message = '', $sessionKey = '', $hideIgnoreWarning = false ) { - global $wgOut; - # Initialize form - $form = new UploadForm( array( - 'watch' => $this->getWatchCheck(), - 'forreupload' => $this->mForReUpload, + $context = new DerivativeContext( $this->getContext() ); + $context->setTitle( $this->getPageTitle() ); // Remove subpage + $form = new UploadForm( [ + 'watch' => $this->getWatchCheck(), + 'forreupload' => $this->mForReUpload, 'sessionkey' => $sessionKey, 'hideignorewarning' => $hideIgnoreWarning, 'destwarningack' => (bool)$this->mDestWarningAck, - + + 'description' => $this->mComment, 'texttop' => $this->uploadFormTextTop, 'textaftersummary' => $this->uploadFormTextAfterSummary, 'destfile' => $this->mDesiredDestName, - ) ); - $form->setTitle( $this->getTitle() ); + ], $context, $this->getLinkRenderer() ); # Check the token, but only if necessary - if( !$this->mTokenOk && !$this->mCancelUpload - && ( $this->mUpload && $this->mUploadClicked ) ) { - $form->addPreText( wfMsgExt( 'session_fail_preview', 'parseinline' ) ); + if ( + !$this->mTokenOk && !$this->mCancelUpload && + ( $this->mUpload && $this->mUploadClicked ) + ) { + $form->addPreText( $this->msg( 'session_fail_preview' )->parse() ); + } + + # Give a notice if the user is uploading a file that has been deleted or moved + # Note that this is independent from the message 'filewasdeleted' + $desiredTitleObj = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName ); + $delNotice = ''; // empty by default + if ( $desiredTitleObj instanceof Title && !$desiredTitleObj->exists() ) { + $dbr = wfGetDB( DB_REPLICA ); + + LogEventsList::showLogExtract( $delNotice, [ 'delete', 'move' ], + $desiredTitleObj, + '', [ 'lim' => 10, + 'conds' => [ 'log_action != ' . $dbr->addQuotes( 'revision' ) ], + 'showIfEmpty' => false, + 'msgKey' => [ 'upload-recreate-warning' ] ] + ); } + $form->addPreText( $delNotice ); # Add text to form - $form->addPreText( '
' . - wfMsgExt( 'uploadtext', 'parse', array( $this->mDesiredDestName ) ) . + $form->addPreText( '
' . + $this->msg( 'uploadtext', [ $this->mDesiredDestName ] )->parseAsBlock() . '
' ); # Add upload error message $form->addPreText( $message ); - + # Add footer to form - $uploadFooter = wfMsgNoTrans( 'uploadfooter' ); - if ( $uploadFooter != '-' && !wfEmptyMsg( 'uploadfooter', $uploadFooter ) ) { + $uploadFooter = $this->msg( 'uploadfooter' ); + if ( !$uploadFooter->isDisabled() ) { $form->addPostText( '\n" ); + . $uploadFooter->parseAsBlock() . "
\n" ); } - - return $form; + return $form; } /** * Shows the "view X deleted revivions link"" */ protected function showViewDeletedLinks() { - global $wgOut, $wgUser; - $title = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName ); + $user = $this->getUser(); // Show a subtitle link to deleted revisions (to sysops et al only) - if( $title instanceof Title ) { + if ( $title instanceof Title ) { $count = $title->isDeleted(); - if ( $count > 0 && $wgUser->isAllowed( 'deletedhistory' ) ) { - $link = wfMsgExt( - $wgUser->isAllowed( 'delete' ) ? 'thisisdeleted' : 'viewdeleted', - array( 'parse', 'replaceafter' ), - $wgUser->getSkin()->linkKnown( - SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedText() ), - wfMsgExt( 'restorelink', array( 'parsemag', 'escape' ), $count ) - ) + if ( $count > 0 && $user->isAllowed( 'deletedhistory' ) ) { + $restorelink = $this->getLinkRenderer()->makeKnownLink( + SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedText() ), + $this->msg( 'restorelink' )->numParams( $count )->text() ); - $wgOut->addHTML( "
{$link}
" ); + $link = $this->msg( $user->isAllowed( 'delete' ) ? 'thisisdeleted' : 'viewdeleted' ) + ->rawParams( $restorelink )->parseAsBlock(); + $this->getOutput()->addHTML( "
{$link}
" ); } } - - // Show the relevant lines from deletion log (for still deleted files only) - if( $title instanceof Title && $title->isDeletedQuick() && !$title->exists() ) { - $this->showDeletionLog( $wgOut, $title->getPrefixedText() ); - } } /** @@ -290,71 +341,125 @@ class SpecialUpload extends SpecialPage { * @param string $message HTML message to be passed to mainUploadForm */ protected function showRecoverableUploadError( $message ) { - $sessionKey = $this->mUpload->stashSession(); - $message = '

' . wfMsgHtml( 'uploadwarning' ) . "

\n" . + $stashStatus = $this->mUpload->tryStashFile( $this->getUser() ); + if ( $stashStatus->isGood() ) { + $sessionKey = $stashStatus->getValue()->getFileKey(); + $uploadWarning = 'upload-tryagain'; + } else { + $sessionKey = null; + $uploadWarning = 'upload-tryagain-nostash'; + } + $message = '

' . $this->msg( 'uploaderror' )->escaped() . "

\n" . '
' . $message . "
\n"; - + $form = $this->getUploadForm( $message, $sessionKey ); - $form->setSubmitText( wfMsg( 'upload-tryagain' ) ); + $form->setSubmitText( $this->msg( $uploadWarning )->escaped() ); $this->showUploadForm( $form ); } + /** - * Stashes the upload, shows the main form, but adds an "continue anyway button". + * Stashes the upload, shows the main form, but adds a "continue anyway button". * Also checks whether there are actually warnings to display. * * @param array $warnings - * @return boolean true if warnings were displayed, false if there are no - * warnings and the should continue processing like there was no warning + * @return bool True if warnings were displayed, false if there are no + * warnings and it should continue processing */ protected function showUploadWarning( $warnings ) { - global $wgUser; - # If there are no warnings, or warnings we can ignore, return early. # mDestWarningAck is set when some javascript has shown the warning # to the user. mForReUpload is set when the user clicks the "upload a # new version" link. - if ( !$warnings || ( count( $warnings ) == 1 && - isset( $warnings['exists'] ) && - ( $this->mDestWarningAck || $this->mForReUpload ) ) ) - { + if ( !$warnings || ( count( $warnings ) == 1 + && isset( $warnings['exists'] ) + && ( $this->mDestWarningAck || $this->mForReUpload ) ) + ) { return false; } - $sessionKey = $this->mUpload->stashSession(); - - $sk = $wgUser->getSkin(); - - $warningHtml = '

' . wfMsgHtml( 'uploadwarning' ) . "

\n" - . '\n"; + $warningHtml .= $this->msg( $uploadWarning )->parseAsBlock(); $form = $this->getUploadForm( $warningHtml, $sessionKey, /* $hideIgnoreWarning */ true ); - $form->setSubmitText( wfMsg( 'upload-tryagain' ) ); - $form->addButton( 'wpUploadIgnoreWarning', wfMsg( 'ignorewarning' ) ); - $form->addButton( 'wpCancelUpload', wfMsg( 'reuploaddesc' ) ); + $form->setSubmitText( $this->msg( 'upload-tryagain' )->text() ); + $form->addButton( [ + 'name' => 'wpUploadIgnoreWarning', + 'value' => $this->msg( 'ignorewarning' )->text() + ] ); + $form->addButton( [ + 'name' => 'wpCancelUpload', + 'value' => $this->msg( 'reuploaddesc' )->text() + ] ); $this->showUploadForm( $form ); - + # Indicate that we showed a form return true; } @@ -362,10 +467,10 @@ class SpecialUpload extends SpecialPage { /** * Show the upload form with error message, but do not stash the file. * - * @param string $message + * @param string $message HTML string */ protected function showUploadError( $message ) { - $message = '

' . wfMsgHtml( 'uploadwarning' ) . "

\n" . + $message = '

' . $this->msg( 'uploadwarning' )->escaped() . "

\n" . '
' . $message . "
\n"; $this->showUploadForm( $this->getUploadForm( $message ) ); } @@ -375,90 +480,157 @@ class SpecialUpload extends SpecialPage { * Checks are made in SpecialUpload::execute() */ protected function processUpload() { - global $wgUser, $wgOut; - - // Verify permissions - $permErrors = $this->mUpload->verifyPermissions( $wgUser ); - if( $permErrors !== true ) { - $wgOut->showPermissionsErrorPage( $permErrors ); - return; - } - // Fetch the file if required $status = $this->mUpload->fetchFile(); - if( !$status->isOK() ) { - $this->showUploadForm( $this->getUploadForm( $wgOut->parse( $status->getWikiText() ) ) ); + if ( !$status->isOK() ) { + $this->showUploadError( $this->getOutput()->parse( $status->getWikiText() ) ); + return; } - - // Deprecated backwards compatibility hook - if( !wfRunHooks( 'UploadForm:BeforeProcessing', array( &$this ) ) ) - { + // Avoid PHP 7.1 warning of passing $this by reference + $upload = $this; + if ( !Hooks::run( 'UploadForm:BeforeProcessing', [ &$upload ] ) ) { wfDebug( "Hook 'UploadForm:BeforeProcessing' broke processing the file.\n" ); - return array( 'status' => UploadBase::BEFORE_PROCESSING ); + // This code path is deprecated. If you want to break upload processing + // do so by hooking into the appropriate hooks in UploadBase::verifyUpload + // and UploadBase::verifyFile. + // If you use this hook to break uploading, the user will be returned + // an empty form with no error message whatsoever. + return; } - // Upload verification $details = $this->mUpload->verifyUpload(); if ( $details['status'] != UploadBase::OK ) { $this->processVerificationError( $details ); + + return; + } + + // Verify permissions for this title + $permErrors = $this->mUpload->verifyTitlePermissions( $this->getUser() ); + if ( $permErrors !== true ) { + $code = array_shift( $permErrors[0] ); + $this->showRecoverableUploadError( $this->msg( $code, $permErrors[0] )->parse() ); + return; } $this->mLocalFile = $this->mUpload->getLocalFile(); // Check warnings if necessary - if( !$this->mIgnoreWarning ) { + if ( !$this->mIgnoreWarning ) { $warnings = $this->mUpload->checkWarnings(); - if( $this->showUploadWarning( $warnings ) ) { + if ( $this->showUploadWarning( $warnings ) ) { return; } } + // This is as late as we can throttle, after expected issues have been handled + if ( UploadBase::isThrottled( $this->getUser() ) ) { + $this->showRecoverableUploadError( + $this->msg( 'actionthrottledtext' )->escaped() + ); + return; + } + // Get the page text if this is not a reupload - if( !$this->mForReUpload ) { + if ( !$this->mForReUpload ) { $pageText = self::getInitialPageText( $this->mComment, $this->mLicense, - $this->mCopyrightStatus, $this->mCopyrightSource ); + $this->mCopyrightStatus, $this->mCopyrightSource, $this->getConfig() ); } else { $pageText = false; } - $status = $this->mUpload->performUpload( $this->mComment, $pageText, $this->mWatchthis, $wgUser ); + + $changeTags = $this->getRequest()->getVal( 'wpChangeTags' ); + if ( is_null( $changeTags ) || $changeTags === '' ) { + $changeTags = []; + } else { + $changeTags = array_filter( array_map( 'trim', explode( ',', $changeTags ) ) ); + } + + if ( $changeTags ) { + $changeTagsStatus = ChangeTags::canAddTagsAccompanyingChange( + $changeTags, $this->getUser() ); + if ( !$changeTagsStatus->isOK() ) { + $this->showUploadError( $this->getOutput()->parse( $changeTagsStatus->getWikiText() ) ); + + return; + } + } + + $status = $this->mUpload->performUpload( + $this->mComment, + $pageText, + $this->mWatchthis, + $this->getUser(), + $changeTags + ); + if ( !$status->isGood() ) { - $this->showUploadError( $wgOut->parse( $status->getWikiText() ) ); + $this->showRecoverableUploadError( $this->getOutput()->parse( $status->getWikiText() ) ); + return; } // Success, redirect to description page $this->mUploadSuccessful = true; - wfRunHooks( 'SpecialUploadComplete', array( &$this ) ); - $wgOut->redirect( $this->mLocalFile->getTitle()->getFullURL() ); - + // Avoid PHP 7.1 warning of passing $this by reference + $upload = $this; + Hooks::run( 'SpecialUploadComplete', [ &$upload ] ); + $this->getOutput()->redirect( $this->mLocalFile->getTitle()->getFullURL() ); } /** * Get the initial image page text based on a comment and optional file status information + * @param string $comment + * @param string $license + * @param string $copyStatus + * @param string $source + * @param Config $config Configuration object to load data from + * @return string */ - public static function getInitialPageText( $comment = '', $license = '', $copyStatus = '', $source = '' ) { - global $wgUseCopyrightUpload; - if ( $wgUseCopyrightUpload ) { + public static function getInitialPageText( $comment = '', $license = '', + $copyStatus = '', $source = '', Config $config = null + ) { + if ( $config === null ) { + wfDebug( __METHOD__ . ' called without a Config instance passed to it' ); + $config = MediaWikiServices::getInstance()->getMainConfig(); + } + + $msg = []; + $forceUIMsgAsContentMsg = (array)$config->get( 'ForceUIMsgAsContentMsg' ); + /* These messages are transcluded into the actual text of the description page. + * Thus, forcing them as content messages makes the upload to produce an int: template + * instead of hardcoding it there in the uploader language. + */ + foreach ( [ 'license-header', 'filedesc', 'filestatus', 'filesource' ] as $msgName ) { + if ( in_array( $msgName, $forceUIMsgAsContentMsg ) ) { + $msg[$msgName] = "{{int:$msgName}}"; + } else { + $msg[$msgName] = wfMessage( $msgName )->inContentLanguage()->text(); + } + } + + if ( $config->get( 'UseCopyrightUpload' ) ) { $licensetxt = ''; if ( $license != '' ) { - $licensetxt = '== ' . wfMsgForContent( 'license-header' ) . " ==\n" . '{{' . $license . '}}' . "\n"; + $licensetxt = '== ' . $msg['license-header'] . " ==\n" . '{{' . $license . '}}' . "\n"; } - $pageText = '== ' . wfMsgForContent ( 'filedesc' ) . " ==\n" . $comment . "\n" . - '== ' . wfMsgForContent ( 'filestatus' ) . " ==\n" . $copyStatus . "\n" . - "$licensetxt" . - '== ' . wfMsgForContent ( 'filesource' ) . " ==\n" . $source ; + $pageText = '== ' . $msg['filedesc'] . " ==\n" . $comment . "\n" . + '== ' . $msg['filestatus'] . " ==\n" . $copyStatus . "\n" . + "$licensetxt" . + '== ' . $msg['filesource'] . " ==\n" . $source; } else { if ( $license != '' ) { - $filedesc = $comment == '' ? '' : '== ' . wfMsgForContent ( 'filedesc' ) . " ==\n" . $comment . "\n"; - $pageText = $filedesc . - '== ' . wfMsgForContent ( 'license-header' ) . " ==\n" . '{{' . $license . '}}' . "\n"; + $filedesc = $comment == '' ? '' : '== ' . $msg['filedesc'] . " ==\n" . $comment . "\n"; + $pageText = $filedesc . + '== ' . $msg['license-header'] . " ==\n" . '{{' . $license . '}}' . "\n"; } else { $pageText = $comment; } } + return $pageText; } @@ -472,75 +644,91 @@ class SpecialUpload extends SpecialPage { * * Note that the page target can be changed *on the form*, so our check * state can get out of sync. + * @return bool|string */ protected function getWatchCheck() { - global $wgUser; - if( $wgUser->getOption( 'watchdefault' ) ) { + if ( $this->getUser()->getOption( 'watchdefault' ) ) { // Watch all edits! return true; } + $desiredTitleObj = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName ); + if ( $desiredTitleObj instanceof Title && $this->getUser()->isWatched( $desiredTitleObj ) ) { + // Already watched, don't change that + return true; + } + $local = wfLocalFile( $this->mDesiredDestName ); - if( $local && $local->exists() ) { + if ( $local && $local->exists() ) { // We're uploading a new version of an existing file. // No creation, so don't watch it if we're not already. - return $local->getTitle()->userIsWatching(); + return false; } else { // New page should get watched if that's our option. - return $wgUser->getOption( 'watchcreations' ); + return $this->getUser()->getOption( 'watchcreations' ) || + $this->getUser()->getOption( 'watchuploads' ); } } - /** * Provides output to the user for a result of UploadBase::verifyUpload * * @param array $details Result of UploadBase::verifyUpload + * @throws MWException */ protected function processVerificationError( $details ) { - global $wgFileExtensions, $wgLang; - - switch( $details['status'] ) { - + switch ( $details['status'] ) { /** Statuses that only require name changing **/ case UploadBase::MIN_LENGTH_PARTNAME: - $this->showRecoverableUploadError( wfMsgHtml( 'minlength1' ) ); + $this->showRecoverableUploadError( $this->msg( 'minlength1' )->escaped() ); break; case UploadBase::ILLEGAL_FILENAME: - $this->showRecoverableUploadError( wfMsgExt( 'illegalfilename', - 'parseinline', $details['filtered'] ) ); + $this->showRecoverableUploadError( $this->msg( 'illegalfilename', + $details['filtered'] )->parse() ); break; - case UploadBase::OVERWRITE_EXISTING_FILE: - $this->showRecoverableUploadError( wfMsgExt( $details['overwrite'], - 'parseinline' ) ); + case UploadBase::FILENAME_TOO_LONG: + $this->showRecoverableUploadError( $this->msg( 'filename-toolong' )->escaped() ); break; case UploadBase::FILETYPE_MISSING: - $this->showRecoverableUploadError( wfMsgExt( 'filetype-missing', - 'parseinline' ) ); + $this->showRecoverableUploadError( $this->msg( 'filetype-missing' )->parse() ); + break; + case UploadBase::WINDOWS_NONASCII_FILENAME: + $this->showRecoverableUploadError( $this->msg( 'windows-nonascii-filename' )->parse() ); break; /** Statuses that require reuploading **/ case UploadBase::EMPTY_FILE: - $this->showUploadForm( $this->getUploadForm( wfMsgHtml( 'emptyfile' ) ) ); + $this->showUploadError( $this->msg( 'emptyfile' )->escaped() ); + break; + case UploadBase::FILE_TOO_LARGE: + $this->showUploadError( $this->msg( 'largefileserver' )->escaped() ); break; case UploadBase::FILETYPE_BADTYPE: - $finalExt = $details['finalExt']; - $this->showUploadError( - wfMsgExt( 'filetype-banned-type', - array( 'parseinline' ), - htmlspecialchars( $finalExt ), - implode( - wfMsgExt( 'comma-separator', array( 'escapenoentities' ) ), - $wgFileExtensions - ), - $wgLang->formatNum( count( $wgFileExtensions ) ) - ) - ); + $msg = $this->msg( 'filetype-banned-type' ); + if ( isset( $details['blacklistedExt'] ) ) { + $msg->params( $this->getLanguage()->commaList( $details['blacklistedExt'] ) ); + } else { + $msg->params( $details['finalExt'] ); + } + $extensions = array_unique( $this->getConfig()->get( 'FileExtensions' ) ); + $msg->params( $this->getLanguage()->commaList( $extensions ), + count( $extensions ) ); + + // Add PLURAL support for the first parameter. This results + // in a bit unlogical parameter sequence, but does not break + // old translations + if ( isset( $details['blacklistedExt'] ) ) { + $msg->params( count( $details['blacklistedExt'] ) ); + } else { + $msg->params( 1 ); + } + + $this->showUploadError( $msg->parse() ); break; case UploadBase::VERIFICATION_ERROR: unset( $details['status'] ); $code = array_shift( $details['details'] ); - $this->showUploadError( wfMsgExt( $code, 'parseinline', $details['details'] ) ); + $this->showUploadError( $this->msg( $code, $details['details'] )->parse() ); break; case UploadBase::HOOK_ABORTED: if ( is_array( $details['error'] ) ) { # allow hooks to return error details in an array @@ -551,7 +739,7 @@ class SpecialUpload extends SpecialPage { $args = null; } - $this->showUploadError( wfMsgExt( $error, 'parseinline', $args ) ); + $this->showUploadError( $this->msg( $error, $args )->parse() ); break; default: throw new MWException( __METHOD__ . ": Unknown value `{$details['status']}`" ); @@ -560,16 +748,17 @@ class SpecialUpload extends SpecialPage { /** * Remove a temporarily kept file stashed by saveTempUploadedFile(). - * @access private - * @return success + * + * @return bool Success */ protected function unsaveUploadedFile() { - global $wgOut; - if ( !( $this->mUpload instanceof UploadFromStash ) ) + if ( !( $this->mUpload instanceof UploadFromStash ) ) { return true; + } $success = $this->mUpload->unsaveUploadedFile(); - if ( ! $success ) { - $wgOut->showFileDeleteError( $this->mUpload->getTempPath() ); + if ( !$success ) { + $this->getOutput()->showFileDeleteError( $this->mUpload->getTempPath() ); + return false; } else { return true; @@ -586,474 +775,75 @@ class SpecialUpload extends SpecialPage { * @return string Empty string if there is no warning or an HTML fragment */ public static function getExistsWarning( $exists ) { - global $wgUser, $wgContLang; - - if ( !$exists ) + if ( !$exists ) { return ''; + } $file = $exists['file']; $filename = $file->getTitle()->getPrefixedText(); - $warning = ''; + $warnMsg = null; - $sk = $wgUser->getSkin(); - - if( $exists['warning'] == 'exists' ) { + if ( $exists['warning'] == 'exists' ) { // Exact match - $warning = wfMsgExt( 'fileexists', 'parseinline', $filename ); - } elseif( $exists['warning'] == 'page-exists' ) { + $warnMsg = wfMessage( 'fileexists', $filename ); + } elseif ( $exists['warning'] == 'page-exists' ) { // Page exists but file does not - $warning = wfMsgExt( 'filepageexists', 'parseinline', $filename ); + $warnMsg = wfMessage( 'filepageexists', $filename ); } elseif ( $exists['warning'] == 'exists-normalized' ) { - $warning = wfMsgExt( 'fileexists-extension', 'parseinline', $filename, + $warnMsg = wfMessage( 'fileexists-extension', $filename, $exists['normalizedFile']->getTitle()->getPrefixedText() ); } elseif ( $exists['warning'] == 'thumb' ) { // Swapped argument order compared with other messages for backwards compatibility - $warning = wfMsgExt( 'fileexists-thumbnail-yes', 'parseinline', + $warnMsg = wfMessage( 'fileexists-thumbnail-yes', $exists['thumbFile']->getTitle()->getPrefixedText(), $filename ); } elseif ( $exists['warning'] == 'thumb-name' ) { // Image w/o '180px-' does not exists, but we do not like these filenames $name = $file->getName(); $badPart = substr( $name, 0, strpos( $name, '-' ) + 1 ); - $warning = wfMsgExt( 'file-thumbnail-no', 'parseinline', $badPart ); + $warnMsg = wfMessage( 'file-thumbnail-no', $badPart ); } elseif ( $exists['warning'] == 'bad-prefix' ) { - $warning = wfMsgExt( 'filename-bad-prefix', 'parseinline', $exists['prefix'] ); - } elseif ( $exists['warning'] == 'was-deleted' ) { - # If the file existed before and was deleted, warn the user of this - $ltitle = SpecialPage::getTitleFor( 'Log' ); - $llink = $sk->linkKnown( - $ltitle, - wfMsgHtml( 'deletionlog' ), - array(), - array( - 'type' => 'delete', - 'page' => $filename - ) - ); - $warning = wfMsgWikiHtml( 'filewasdeleted', $llink ); + $warnMsg = wfMessage( 'filename-bad-prefix', $exists['prefix'] ); } - return $warning; - } - - /** - * Get a list of warnings - * - * @param string local filename, e.g. 'file exists', 'non-descriptive filename' - * @return array list of warning messages - */ - public static function ajaxGetExistsWarning( $filename ) { - $file = wfFindFile( $filename ); - if( !$file ) { - // Force local file so we have an object to do further checks against - // if there isn't an exact match... - $file = wfLocalFile( $filename ); - } - $s = ' '; - if ( $file ) { - $exists = UploadBase::getExistsWarning( $file ); - $warning = self::getExistsWarning( $exists ); - if ( $warning !== '' ) { - $s = "
$warning
"; - } - } - return $s; + return $warnMsg ? $warnMsg->title( $file->getTitle() )->parse() : ''; } /** * Construct a warning and a gallery from an array of duplicate files. + * @param array $dupes + * @return string */ - public static function getDupeWarning( $dupes ) { - if( $dupes ) { - global $wgOut; - $msg = ""; - foreach( $dupes as $file ) { - $title = $file->getTitle(); - $msg .= $title->getPrefixedText() . - "|" . $title->getText() . "\n"; - } - $msg .= ""; - return "
  • " . - wfMsgExt( "file-exists-duplicate", array( "parse" ), count( $dupes ) ) . - $wgOut->parse( $msg ) . - "
  • \n"; - } else { + public function getDupeWarning( $dupes ) { + if ( !$dupes ) { return ''; } - } - -} -/** - * Sub class of HTMLForm that provides the form section of SpecialUpload - */ -class UploadForm extends HTMLForm { - protected $mWatch; - protected $mForReUpload; - protected $mSessionKey; - protected $mHideIgnoreWarning; - protected $mDestWarningAck; - protected $mDestFile; - - protected $mTextTop; - protected $mTextAfterSummary; - - protected $mSourceIds; - - public function __construct( $options = array() ) { - global $wgLang; - - $this->mWatch = !empty( $options['watch'] ); - $this->mForReUpload = !empty( $options['forreupload'] ); - $this->mSessionKey = isset( $options['sessionkey'] ) - ? $options['sessionkey'] : ''; - $this->mHideIgnoreWarning = !empty( $options['hideignorewarning'] ); - $this->mDestWarningAck = !empty( $options['destwarningack'] ); - - $this->mTextTop = $options['texttop']; - $this->mTextAfterSummary = $options['textaftersummary']; - $this->mDestFile = isset( $options['destfile'] ) ? $options['destfile'] : ''; - - $sourceDescriptor = $this->getSourceSection(); - $descriptor = $sourceDescriptor - + $this->getDescriptionSection() - + $this->getOptionsSection(); - - wfRunHooks( 'UploadFormInitDescriptor', array( &$descriptor ) ); - parent::__construct( $descriptor, 'upload' ); - - # Set some form properties - $this->setSubmitText( wfMsg( 'uploadbtn' ) ); - $this->setSubmitName( 'wpUpload' ); - $this->setSubmitTooltip( 'upload' ); - $this->setId( 'mw-upload-form' ); - - # Build a list of IDs for javascript insertion - $this->mSourceIds = array(); - foreach ( $sourceDescriptor as $key => $field ) { - if ( !empty( $field['id'] ) ) - $this->mSourceIds[] = $field['id']; + $gallery = ImageGalleryBase::factory( false, $this->getContext() ); + $gallery->setShowBytes( false ); + $gallery->setShowDimensions( false ); + foreach ( $dupes as $file ) { + $gallery->add( $file->getTitle() ); } + return '
  • ' . + $this->msg( 'file-exists-duplicate' )->numParams( count( $dupes ) )->parse() . + $gallery->toHTML() . "
  • \n"; } - /** - * Get the descriptor of the fieldset that contains the file source - * selection. The section is 'source' - * - * @return array Descriptor array - */ - protected function getSourceSection() { - global $wgLang, $wgUser, $wgRequest; - - if ( $this->mSessionKey ) { - return array( - 'wpSessionKey' => array( - 'type' => 'hidden', - 'default' => $this->mSessionKey, - ), - 'wpSourceType' => array( - 'type' => 'hidden', - 'default' => 'Stash', - ), - ); - } - - $canUploadByUrl = UploadFromUrl::isEnabled() && $wgUser->isAllowed( 'upload_by_url' ); - $radio = $canUploadByUrl; - $selectedSourceType = strtolower( $wgRequest->getText( 'wpSourceType', 'File' ) ); - - $descriptor = array(); - if ( $this->mTextTop ) { - $descriptor['UploadFormTextTop'] = array( - 'type' => 'info', - 'section' => 'source', - 'default' => $this->mTextTop, - 'raw' => true, - ); - } - - $descriptor['UploadFile'] = array( - 'class' => 'UploadSourceField', - 'section' => 'source', - 'type' => 'file', - 'id' => 'wpUploadFile', - 'label-message' => 'sourcefilename', - 'upload-type' => 'File', - 'radio' => &$radio, - 'help' => wfMsgExt( 'upload-maxfilesize', - array( 'parseinline', 'escapenoentities' ), - $wgLang->formatSize( - wfShorthandToInteger( ini_get( 'upload_max_filesize' ) ) - ) - ) . ' ' . wfMsgHtml( 'upload_source_file' ), - 'checked' => $selectedSourceType == 'file', - ); - if ( $canUploadByUrl ) { - global $wgMaxUploadSize; - $descriptor['UploadFileURL'] = array( - 'class' => 'UploadSourceField', - 'section' => 'source', - 'id' => 'wpUploadFileURL', - 'label-message' => 'sourceurl', - 'upload-type' => 'url', - 'radio' => &$radio, - 'help' => wfMsgExt( 'upload-maxfilesize', - array( 'parseinline', 'escapenoentities' ), - $wgLang->formatSize( $wgMaxUploadSize ) - ) . ' ' . wfMsgHtml( 'upload_source_url' ), - 'checked' => $selectedSourceType == 'url', - ); - } - wfRunHooks( 'UploadFormSourceDescriptors', array( &$descriptor, &$radio, $selectedSourceType ) ); - - $descriptor['Extensions'] = array( - 'type' => 'info', - 'section' => 'source', - 'default' => $this->getExtensionsMessage(), - 'raw' => true, - ); - return $descriptor; - } - - - /** - * Get the messages indicating which extensions are preferred and prohibitted. - * - * @return string HTML string containing the message - */ - protected function getExtensionsMessage() { - # Print a list of allowed file extensions, if so configured. We ignore - # MIME type here, it's incomprehensible to most people and too long. - global $wgLang, $wgCheckFileExtensions, $wgStrictFileExtensions, - $wgFileExtensions, $wgFileBlacklist; - - $allowedExtensions = ''; - if( $wgCheckFileExtensions ) { - if( $wgStrictFileExtensions ) { - # Everything not permitted is banned - $extensionsList = - '
    ' . - wfMsgWikiHtml( 'upload-permitted', $wgLang->commaList( $wgFileExtensions ) ) . - "
    \n"; - } else { - # We have to list both preferred and prohibited - $extensionsList = - '
    ' . - wfMsgWikiHtml( 'upload-preferred', $wgLang->commaList( $wgFileExtensions ) ) . - "
    \n" . - '
    ' . - wfMsgWikiHtml( 'upload-prohibited', $wgLang->commaList( $wgFileBlacklist ) ) . - "
    \n"; - } - } else { - # Everything is permitted. - $extensionsList = ''; - } - return $extensionsList; + protected function getGroupName() { + return 'media'; } /** - * Get the descriptor of the fieldset that contains the file description - * input. The section is 'description' - * - * @return array Descriptor array - */ - protected function getDescriptionSection() { - global $wgUser, $wgOut; - - $cols = intval( $wgUser->getOption( 'cols' ) ); - if( $wgUser->getOption( 'editwidth' ) ) { - $wgOut->addInlineStyle( '#mw-htmlform-description { width: 100%; }' ); - } - - $descriptor = array( - 'DestFile' => array( - 'type' => 'text', - 'section' => 'description', - 'id' => 'wpDestFile', - 'label-message' => 'destfilename', - 'size' => 60, - 'default' => $this->mDestFile, - # FIXME: hack to work around poor handling of the 'default' option in HTMLForm - 'nodata' => strval( $this->mDestFile ) !== '', - ), - 'UploadDescription' => array( - 'type' => 'textarea', - 'section' => 'description', - 'id' => 'wpUploadDescription', - 'label-message' => $this->mForReUpload - ? 'filereuploadsummary' - : 'fileuploadsummary', - 'cols' => $cols, - 'rows' => 8, - ) - ); - if ( $this->mTextAfterSummary ) { - $descriptor['UploadFormTextAfterSummary'] = array( - 'type' => 'info', - 'section' => 'description', - 'default' => $this->mTextAfterSummary, - 'raw' => true, - ); - } - - $descriptor += array( - 'EditTools' => array( - 'type' => 'edittools', - 'section' => 'description', - ), - 'License' => array( - 'type' => 'select', - 'class' => 'Licenses', - 'section' => 'description', - 'id' => 'wpLicense', - 'label-message' => 'license', - ), - ); - if ( $this->mForReUpload ) - $descriptor['DestFile']['readonly'] = true; - - global $wgUseCopyrightUpload; - if ( $wgUseCopyrightUpload ) { - $descriptor['UploadCopyStatus'] = array( - 'type' => 'text', - 'section' => 'description', - 'id' => 'wpUploadCopyStatus', - 'label-message' => 'filestatus', - ); - $descriptor['UploadSource'] = array( - 'type' => 'text', - 'section' => 'description', - 'id' => 'wpUploadSource', - 'label-message' => 'filesource', - ); - } - - return $descriptor; - } - - /** - * Get the descriptor of the fieldset that contains the upload options, - * such as "watch this file". The section is 'options' - * - * @return array Descriptor array - */ - protected function getOptionsSection() { - global $wgUser, $wgOut; - - if( $wgUser->isLoggedIn() ) { - $descriptor = array( - 'Watchthis' => array( - 'type' => 'check', - 'id' => 'wpWatchthis', - 'label-message' => 'watchthisupload', - 'section' => 'options', - 'default' => $wgUser->getOption( 'watchcreations' ), - ) - ); - } - if( !$this->mHideIgnoreWarning ) { - $descriptor['IgnoreWarning'] = array( - 'type' => 'check', - 'id' => 'wpIgnoreWarning', - 'label-message' => 'ignorewarnings', - 'section' => 'options', - ); - } - - $descriptor['wpDestFileWarningAck'] = array( - 'type' => 'hidden', - 'id' => 'wpDestFileWarningAck', - 'default' => $this->mDestWarningAck ? '1' : '', - ); - - if ( $this->mForReUpload ) { - $descriptor['wpForReUpload'] = array( - 'type' => 'hidden', - 'id' => 'wpForReUpload', - 'default' => '1', - ); - } - - return $descriptor; - - } - - /** - * Add the upload JS and show the form. - */ - public function show() { - $this->addUploadJS(); - parent::show(); - } - - /** - * Add upload JS to $wgOut - * - * @param bool $autofill Whether or not to autofill the destination - * filename text box - */ - protected function addUploadJS( ) { - global $wgUseAjax, $wgAjaxUploadDestCheck, $wgAjaxLicensePreview, $wgEnableAPI; - global $wgOut; - - $useAjaxDestCheck = $wgUseAjax && $wgAjaxUploadDestCheck; - $useAjaxLicensePreview = $wgUseAjax && $wgAjaxLicensePreview && $wgEnableAPI; - - $scriptVars = array( - 'wgAjaxUploadDestCheck' => $useAjaxDestCheck, - 'wgAjaxLicensePreview' => $useAjaxLicensePreview, - 'wgUploadAutoFill' => !$this->mForReUpload && - // If we received mDestFile from the request, don't autofill - // the wpDestFile textbox - $this->mDestFile === '', - 'wgUploadSourceIds' => $this->mSourceIds, - ); - - $wgOut->addScript( Skin::makeVariablesScript( $scriptVars ) ); - - // For support - $wgOut->addScriptFile( 'edit.js' ); - $wgOut->addScriptFile( 'upload.js' ); - } - - /** - * Empty function; submission is handled elsewhere. - * - * @return bool false + * Should we rotate images in the preview on Special:Upload. + * + * This controls js: mw.config.get( 'wgFileCanRotate' ) + * + * @todo What about non-BitmapHandler handled files? + * @return bool */ - function trySubmit() { - return false; + public static function rotationEnabled() { + $bitmapHandler = new BitmapHandler(); + return $bitmapHandler->autoRotateEnabled(); } - } - -/** - * A form field that contains a radio box in the label - */ -class UploadSourceField extends HTMLTextField { - function getLabelHtml() { - $id = "wpSourceType{$this->mParams['upload-type']}"; - $label = Html::rawElement( 'label', array( 'for' => $id ), $this->mLabel ); - - if ( !empty( $this->mParams['radio'] ) ) { - $attribs = array( - 'name' => 'wpSourceType', - 'type' => 'radio', - 'id' => $id, - 'value' => $this->mParams['upload-type'], - ); - if ( !empty( $this->mParams['checked'] ) ) - $attribs['checked'] = 'checked'; - $label .= Html::element( 'input', $attribs ); - } - - return Html::rawElement( 'td', array( 'class' => 'mw-label' ), $label ); - } - function getSize() { - return isset( $this->mParams['size'] ) - ? $this->mParams['size'] - : 60; - } -} -