X-Git-Url: https://git.cyclocoop.org/%242?a=blobdiff_plain;f=includes%2Fspecials%2FSpecialUpload.php;h=7a789ae29c9fd03eb1cda81a01dfb6173382c88d;hb=1a450f99fb5fe56d79b90415003b9434240e9da5;hp=6b2f4dcce384b1f0313cea945b12b90db48e0620;hpb=1354117171fc14332790699100f273a8b90d80c7;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/specials/SpecialUpload.php b/includes/specials/SpecialUpload.php index 6b2f4dcce3..7a789ae29c 100644 --- a/includes/specials/SpecialUpload.php +++ b/includes/specials/SpecialUpload.php @@ -2,109 +2,141 @@ /** * @file * @ingroup SpecialPage + * @ingroup Upload + * + * Form for handling uploads and special page. */ -/** - * implements Special:Upload - * @ingroup SpecialPage - */ -class UploadForm extends SpecialPage { - /**#@+ - * @access private - */ - var $mComment, $mLicense, $mIgnoreWarning; - var $mCopyrightStatus, $mCopyrightSource, $mReUpload, $mAction, $mUploadClicked; - var $mDestWarningAck; - var $mLocalFile; - - var $mUpload; // Instance of UploadBase or derivative - - # Placeholders for text injection by hooks (must be HTML) - # extensions should take care to _append_ to the present value - var $uploadFormTextTop; - var $uploadFormTextAfterSummary; - var $mTokenOk = false; - var $mForReUpload = false; - /**#@-*/ - +class SpecialUpload extends SpecialPage { /** * Constructor : initialise object * Get data POSTed through the form and assign them to the object - * @param $request Data posted. + * @param $request WebRequest : data posted. */ - function __construct( $request = null ) { - global $wgUser, $wgRequest; + public function __construct( $request = null ) { + global $wgRequest; - parent::__construct( 'Upload' ); + parent::__construct( 'Upload', 'upload' ); - $this->setHeaders(); - $this->outputHeader(); + $this->loadRequest( is_null( $request ) ? $wgRequest : $request ); + } - if ( is_null( $request ) ) { - $request = $wgRequest; - } + /** Misc variables **/ + protected $mRequest; // The WebRequest or FauxRequest this form is supposed to handle + protected $mSourceType; + protected $mUpload; + protected $mLocalFile; + protected $mUploadClicked; + + /** User input variables from the "description" section **/ + public $mDesiredDestName; // The requested target file name + protected $mComment; + protected $mLicense; + + /** User input variables from the root section **/ + protected $mIgnoreWarning; + protected $mWatchThis; + protected $mCopyrightStatus; + protected $mCopyrightSource; + + /** Hidden variables **/ + protected $mDestWarningAck; + protected $mForReUpload; // The user followed an "overwrite this file" link + protected $mCancelUpload; // The user clicked "Cancel and return to upload form" button + protected $mTokenOk; + protected $mUploadSuccessful = false; // Subclasses can use this to determine whether a file was uploaded + + /** Text injection points for hooks not using HTMLForm **/ + public $uploadFormTextTop; + public $uploadFormTextAfterSummary; + + /** + * Initialize instance variables from request and create an Upload handler + * + * @param $request WebRequest: 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' ) + || $request->getCheck( 'wpUploadIgnoreWarning' ) ); // Guess the desired name from the filename if not provided $this->mDesiredDestName = $request->getText( 'wpDestFile' ); - if( !$this->mDesiredDestName ) + if( !$this->mDesiredDestName ) { $this->mDesiredDestName = $request->getText( 'wpUploadFile' ); - - $this->mIgnoreWarning = $request->getCheck( 'wpIgnoreWarning' ); - $this->mComment = $request->getText( 'wpUploadDescription' ); - - if( !$request->wasPosted() ) { - # GET requests just give the main form; no data except destination - # filename and description - return; } - //if it was posted check for the token (no remote POST'ing with user credentials) - $token = $request->getVal( 'wpEditToken' ); - $this->mTokenOk = $wgUser->matchEditToken( $token ); - - # Placeholders for text injection by hooks (empty per default) - $this->uploadFormTextTop = ""; - $this->uploadFormTextAfterSummary = ""; + $this->mComment = $request->getText( 'wpUploadDescription' ); + $this->mLicense = $request->getText( 'wpLicense' ); - $this->mUploadClicked = $request->getCheck( 'wpUpload' ); - $this->mLicense = $request->getText( 'wpLicense' ); + $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->mSourceType = $request->getText( 'wpSourceType' ); - $this->mDestWarningAck = $request->getText( 'wpDestFileWarningAck' ); - $this->mForReUpload = $request->getBool( 'wpForReUpload' ); - $this->mReUpload = $request->getCheck( 'wpReUpload' ); + $this->mForReUpload = $request->getBool( 'wpForReUpload' ); // updating a file + $this->mCancelUpload = $request->getCheck( 'wpCancelUpload' ) + || $request->getCheck( 'wpReUpload' ); // b/w compat - $this->mAction = $request->getVal( 'action' ); - $this->mUpload = UploadBase::createFromRequest( $request ); + // 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->uploadFormTextTop = ''; + $this->uploadFormTextAfterSummary = ''; } + /** + * This page can be shown if uploading is enabled. + * Handle permission checking elsewhere in order to be able to show + * custom error messages. + * + * @param $user User object + * @return Boolean + */ + public function userCanExecute( $user ) { + return UploadBase::isEnabled() && parent::userCanExecute( $user ); + } /** - * Start doing stuff - * @access public + * Special page entry point */ - function execute( $par ) { - global $wgUser, $wgOut; + public function execute( $par ) { + global $wgUser, $wgOut, $wgRequest; + + $this->setHeaders(); + $this->outputHeader(); + # Check uploading enabled if( !UploadBase::isEnabled() ) { $wgOut->showErrorPage( 'uploaddisabled', 'uploaddisabledtext' ); return; } + # Check permissions - if( $this->mUpload ) { - $permission = $this->mUpload->isAllowed( $wgUser ); - } else { - $permission = $wgUser->isAllowed( 'upload' ) ? true : 'upload'; - } - if( $permission !== true ) { - if( !$wgUser->isLoggedIn() ) { + 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( $permission ); + $wgOut->permissionRequired( 'upload' ); } return; } @@ -115,308 +147,503 @@ class UploadForm extends SpecialPage { return; } + # Check whether we actually want to allow changing stuff if( wfReadOnly() ) { $wgOut->readOnlyPage(); return; } - //check token if uploading or reUploading - if( !$this->mTokenOk && !$this->mReUpload && ($this->mUpload && ( - 'submit' == $this->mAction || - $this->mUploadClicked - ) - ) - ){ - $this->mainUploadForm ( wfMsg( 'session_fail_preview' ) ); - return ; - } - - if( $this->mReUpload ) { - // User choose to cancel upload - if( !$this->mUpload->unsaveUploadedFile() ) { + # Unsave the temporary file in case this was a cancelled upload + if ( $this->mCancelUpload ) { + if ( !$this->unsaveUploadedFile() ) { + # Something went wrong, so unsaveUploadedFile showed a warning return; } - # Because it is probably checked and shouldn't be - $this->mIgnoreWarning = false; - $this->mainUploadForm(); - } elseif( $this->mUpload && ( - 'submit' == $this->mAction || - $this->mUploadClicked - ) ) { + } + + # Process upload or show a form + if ( + $this->mTokenOk && !$this->mCancelUpload && + ( $this->mUpload && $this->mUploadClicked ) + ) + { $this->processUpload(); } else { - $this->mainUploadForm(); + # Backwards compatibility hook + if( !wfRunHooks( 'UploadForm:initial', array( &$this ) ) ) { + wfDebug( "Hook 'UploadForm:initial' broke output of the upload form" ); + return; + } + + $this->showUploadForm( $this->getUploadForm() ); } - if( $this->mUpload ) + # Cleanup + if ( $this->mUpload ) { $this->mUpload->cleanupTempFile(); + } } /** - * Do the upload - * Checks are made in SpecialUpload::execute() + * Show the main upload form * - * FIXME this should really use the standard Status class (instead of associative array) - * FIXME would be nice if we refactored this into the upload api. - * (the special upload page is not the only response point that needs clean localized error msgs) - * - * @access private + * @param $form Mixed: an HTMLForm instance or HTML string to show */ - function processUpload(){ - global $wgOut, $wgFileExtensions, $wgLang; - $details = $this->internalProcessUpload(); - switch( $details['status'] ) { - case UploadBase::SUCCESS: - $wgOut->redirect( $this->mLocalFile->getTitle()->getFullURL() ); - break; + protected function showUploadForm( $form ) { + # Add links if file was previously deleted + if ( !$this->mDesiredDestName ) { + $this->showViewDeletedLinks(); + } - case UploadBase::BEFORE_PROCESSING: - $this->uploadError( $details['error'] ); - break; - case UploadBase::LARGE_FILE_SERVER: - $this->mainUploadForm( wfMsgHtml( 'largefileserver' ) ); - break; + if ( $form instanceof HTMLForm ) { + $form->show(); + } else { + global $wgOut; + $wgOut->addHTML( $form ); + } - case UploadBase::EMPTY_FILE: - $this->mainUploadForm( wfMsgHtml( 'emptyfile' ) ); - break; + } - case UploadBase::MIN_LENGTH_PARTNAME: - $this->mainUploadForm( wfMsgHtml( 'minlength1' ) ); - break; + /** + * Get an UploadForm instance with title and text properly set. + * + * @param $message String: HTML string to add to the form + * @param $sessionKey String: session key in case this is a stashed upload + * @param $hideIgnoreWarning Boolean: whether to hide "ignore warning" check box + * @return UploadForm + */ + protected function getUploadForm( $message = '', $sessionKey = '', $hideIgnoreWarning = false ) { + global $wgOut; - case UploadBase::ILLEGAL_FILENAME: - $this->uploadError( wfMsgExt( 'illegalfilename', - 'parseinline', $details['filtered'] ) ); - break; + # Initialize form + $form = new UploadForm( array( + 'watch' => $this->getWatchCheck(), + 'forreupload' => $this->mForReUpload, + 'sessionkey' => $sessionKey, + 'hideignorewarning' => $hideIgnoreWarning, + 'destwarningack' => (bool)$this->mDestWarningAck, + + 'texttop' => $this->uploadFormTextTop, + 'textaftersummary' => $this->uploadFormTextAfterSummary, + ) ); + $form->setTitle( $this->getTitle() ); + + # Check the token, but only if necessary + if( + !$this->mTokenOk && !$this->mCancelUpload && + ( $this->mUpload && $this->mUploadClicked ) + ) + { + $form->addPreText( wfMsgExt( 'session_fail_preview', 'parseinline' ) ); + } - case UploadBase::PROTECTED_PAGE: - $wgOut->showPermissionsErrorPage( $details['permissionserrors'] ); - break; + # 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' that requires JS + $desiredTitleObj = Title::newFromText( $this->mDesiredDestName, NS_FILE ); + $delNotice = ''; // empty by default + if ( $desiredTitleObj instanceof Title && !$desiredTitleObj->exists() ) { + LogEventsList::showLogExtract( &$delNotice, array( 'delete', 'move' ), + $desiredTitleObj->getPrefixedText(), + '', array( 'lim' => 10, + 'conds' => array( "log_action != 'revision'" ), + 'showIfEmpty' => false, + 'msgKey' => array( 'upload-recreate-warning' ) ) + ); + } + $form->addPreText( $delNotice ); + + # Add text to form + $form->addPreText( '
' . + wfMsgExt( 'uploadtext', 'parse', array( $this->mDesiredDestName ) ) . + '
' ); + # Add upload error message + $form->addPreText( $message ); + + # Add footer to form + $uploadFooter = wfMsgNoTrans( 'uploadfooter' ); + if ( $uploadFooter != '-' && !wfEmptyMsg( 'uploadfooter', $uploadFooter ) ) { + $form->addPostText( '\n" ); + } - case UploadBase::OVERWRITE_EXISTING_FILE: - $this->uploadError( wfMsgExt( $details['overwrite'], - 'parseinline' ) ); - break; + return $form; - case UploadBase::FILETYPE_MISSING: - $this->uploadError( wfMsgExt( 'filetype-missing', array ( 'parseinline' ) ) ); - break; + } - case UploadBase::FILETYPE_BADTYPE: - $finalExt = $details['finalExt']; - $this->uploadError( - wfMsgExt( 'filetype-banned-type', - array( 'parseinline' ), - htmlspecialchars( $finalExt ), - implode( - wfMsgExt( 'comma-separator', array( 'escapenoentities' ) ), - $wgFileExtensions - ), - $wgLang->formatNum( count( $wgFileExtensions ) ) + /** + * Shows the "view X deleted revivions link"" + */ + protected function showViewDeletedLinks() { + global $wgOut, $wgUser; + + $title = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName ); + // Show a subtitle link to deleted revisions (to sysops et al only) + 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 ) ) ); - break; - - case UploadBase::VERIFICATION_ERROR: - unset( $details['status'] ); - $code = array_shift( $details ); - $this->uploadError( wfMsgExt( $code, 'parseinline', $details ) ); - break; + $wgOut->addHTML( "
{$link}
" ); + } + } - case UploadBase::UPLOAD_VERIFICATION_ERROR: - $error = $details['error']; - $this->uploadError( wfMsgExt( $error, 'parseinline' ) ); - break; + // 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() ); + } + } - case UploadBase::UPLOAD_WARNING: - unset( $details['status'] ); - $this->uploadWarning( $details ); - break; + /** + * Stashes the upload and shows the main upload form. + * + * Note: only errors that can be handled by changing the name or + * description should be redirected here. It should be assumed that the + * file itself is sane and has passed UploadBase::verifyFile. This + * essentially means that UploadBase::VERIFICATION_ERROR and + * UploadBase::EMPTY_FILE should not be passed here. + * + * @param $message String: HTML message to be passed to mainUploadForm + */ + protected function showRecoverableUploadError( $message ) { + $sessionKey = $this->mUpload->stashSession(); + $message = '

' . wfMsgHtml( 'uploadwarning' ) . "

\n" . + '
' . $message . "
\n"; + + $form = $this->getUploadForm( $message, $sessionKey ); + $form->setSubmitText( wfMsg( 'upload-tryagain' ) ); + $this->showUploadForm( $form ); + } + /** + * Stashes the upload, shows the main form, but adds an "continue anyway button". + * Also checks whether there are actually warnings to display. + * + * @param $warnings Array + * @return boolean true if warnings were displayed, false if there are no + * warnings and the should continue processing like there was no warning + */ + protected function showUploadWarning( $warnings ) { + # 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 ) ) ) + { + return false; + } - case UploadBase::INTERNAL_ERROR: - $status = $details['internal']; - $this->showError( $wgOut->parse( $status->getWikiText() ) ); - break; + $sessionKey = $this->mUpload->stashSession(); - default: - throw new MWException( __METHOD__ . ": Unknown value `{$details['status']}`" ); + $warningHtml = '

' . wfMsgHtml( 'uploadwarning' ) . "

\n" + . '\n"; + $warningHtml .= wfMsgExt( 'uploadwarning-text', 'parse' ); + + $form = $this->getUploadForm( $warningHtml, $sessionKey, /* $hideIgnoreWarning */ true ); + $form->setSubmitText( wfMsg( 'upload-tryagain' ) ); + $form->addButton( 'wpUploadIgnoreWarning', wfMsg( 'ignorewarning' ) ); + $form->addButton( 'wpCancelUpload', wfMsg( 'reuploaddesc' ) ); + + $this->showUploadForm( $form ); + + # Indicate that we showed a form + return true; } /** - * Really do the upload - * Checks are made in SpecialUpload::execute() - * - * @param array $resultDetails contains result-specific dict of additional values + * Show the upload form with error message, but do not stash the file. * - * @access private + * @param $message HTML string */ - function internalProcessUpload() { - global $wgUser; + protected function showUploadError( $message ) { + $message = '

' . wfMsgHtml( 'uploadwarning' ) . "

\n" . + '
' . $message . "
\n"; + $this->showUploadForm( $this->getUploadForm( $message ) ); + } - if( !wfRunHooks( 'UploadForm:BeforeProcessing', array( &$this ) ) ) - { - wfDebug( "Hook 'UploadForm:BeforeProcessing' broke processing the file.\n" ); - return array( 'status' => UploadBase::BEFORE_PROCESSING ); - } + /** + * Do the upload. + * Checks are made in SpecialUpload::execute() + */ + protected function processUpload() { + global $wgUser, $wgOut; - /** - * If the image is protected, non-sysop users won't be able - * to modify it by uploading a new revision. - */ + // Verify permissions $permErrors = $this->mUpload->verifyPermissions( $wgUser ); if( $permErrors !== true ) { - return array( 'status' => UploadBase::PROTECTED_PAGE, 'permissionserrors' => $permErrors ); + $wgOut->showPermissionsErrorPage( $permErrors ); + return; + } + + if( $this->mUpload instanceOf UploadFromUrl ) { + return $this->showUploadError( wfMsg( 'uploadfromurl-queued' ) ); } // Fetch the file if required $status = $this->mUpload->fetchFile(); - if( !$status->isOK() ){ - return array( 'status' =>UploadBase::BEFORE_PROCESSING, 'error'=>$status->getWikiText() ); + if( !$status->isOK() ) { + $this->showUploadError( $wgOut->parse( $status->getWikiText() ) ); + return; } - // Check whether this is a sane upload - $result = $this->mUpload->verifyUpload(); - if( $result != UploadBase::OK ) - return $result; + // Deprecated backwards compatibility hook + if( !wfRunHooks( 'UploadForm:BeforeProcessing', array( &$this ) ) ) { + wfDebug( "Hook 'UploadForm:BeforeProcessing' broke processing the file.\n" ); + return array( 'status' => UploadBase::BEFORE_PROCESSING ); + } + + + // Upload verification + $details = $this->mUpload->verifyUpload(); + if ( $details['status'] != UploadBase::OK ) { + $this->processVerificationError( $details ); + return; + } $this->mLocalFile = $this->mUpload->getLocalFile(); + // Check warnings if necessary if( !$this->mIgnoreWarning ) { $warnings = $this->mUpload->checkWarnings(); - - if( count( $warnings ) ) { - $warnings['status'] = UploadBase::UPLOAD_WARNING; - return $warnings; + if( $this->showUploadWarning( $warnings ) ) { + return; } } - - /** - * Try actually saving the thing... - * It will show an error form on failure. No it will not. - */ + // Get the page text if this is not a reupload if( !$this->mForReUpload ) { $pageText = self::getInitialPageText( $this->mComment, $this->mLicense, $this->mCopyrightStatus, $this->mCopyrightSource ); + } else { + $pageText = false; } $status = $this->mUpload->performUpload( $this->mComment, $pageText, $this->mWatchthis, $wgUser ); - if ( !$status->isGood() ) { - return array( 'status' => UploadBase::INTERNAL_ERROR, 'internal' => $status ); - } else { - // Success, redirect to description page - wfRunHooks( 'SpecialUploadComplete', array( &$this ) ); - return UploadBase::SUCCESS; + $this->showUploadError( $wgOut->parse( $status->getWikiText() ) ); + return; } + + // Success, redirect to description page + $this->mUploadSuccessful = true; + wfRunHooks( 'SpecialUploadComplete', array( &$this ) ); + $wgOut->redirect( $this->mLocalFile->getTitle()->getFullURL() ); } /** - * Do existence checks on a file and produce a warning - * This check is static and can be done pre-upload via AJAX - * Returns an HTML fragment consisting of one or more LI elements if there is a warning - * Returns an empty string if there is no warning + * Get the initial image page text based on a comment and optional file status information */ - static function getExistsWarning( $exists ) { - global $wgUser, $wgContLang; - - if( $exists === false ) - return ''; + public static function getInitialPageText( $comment = '', $license = '', $copyStatus = '', $source = '' ) { + global $wgUseCopyrightUpload; + if ( $wgUseCopyrightUpload ) { + $licensetxt = ''; + if ( $license != '' ) { + $licensetxt = '== ' . wfMsgForContent( 'license-header' ) . " ==\n" . '{{' . $license . '}}' . "\n"; + } + $pageText = '== ' . wfMsgForContent( 'filedesc' ) . " ==\n" . $comment . "\n" . + '== ' . wfMsgForContent( 'filestatus' ) . " ==\n" . $copyStatus . "\n" . + "$licensetxt" . + '== ' . wfMsgForContent( 'filesource' ) . " ==\n" . $source; + } else { + if ( $license != '' ) { + $filedesc = $comment == '' ? '' : '== ' . wfMsgForContent( 'filedesc' ) . " ==\n" . $comment . "\n"; + $pageText = $filedesc . + '== ' . wfMsgForContent( 'license-header' ) . " ==\n" . '{{' . $license . '}}' . "\n"; + } else { + $pageText = $comment; + } + } + return $pageText; + } - $warning = ''; - $align = $wgContLang->isRtl() ? 'left' : 'right'; + /** + * See if we should check the 'watch this page' checkbox on the form + * based on the user's preferences and whether we're being asked + * to create a new file or update an existing one. + * + * In the case where 'watch edits' is off but 'watch creations' is on, + * we'll leave the box unchecked. + * + * Note that the page target can be changed *on the form*, so our check + * state can get out of sync. + */ + protected function getWatchCheck() { + global $wgUser; + if( $wgUser->getOption( 'watchdefault' ) ) { + // Watch all edits! + return true; + } - list( $existsType, $file ) = $exists; + $local = wfLocalFile( $this->mDesiredDestName ); + 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(); + } else { + // New page should get watched if that's our option. + return $wgUser->getOption( 'watchcreations' ); + } + } - $sk = $wgUser->getSkin(); - if( $existsType == 'exists' ) { - // Exact match - $dlink = $sk->makeKnownLinkObj( $file->getTitle() ); - if ( $file->allowInlineDisplay() ) { - $dlink2 = $sk->makeImageLinkObj( $file->getTitle(), wfMsgExt( 'fileexists-thumb', 'parseinline' ), - $file->getName(), $align, array(), false, true ); - } elseif ( !$file->allowInlineDisplay() && $file->isSafeFile() ) { - $icon = $file->iconThumb(); - $dlink2 = '
' . - $icon->toHtml( array( 'desc-link' => true ) ) . '
' . $dlink . '
'; - } else { - $dlink2 = ''; - } + /** + * Provides output to the user for a result of UploadBase::verifyUpload + * + * @param $details Array: result of UploadBase::verifyUpload + */ + protected function processVerificationError( $details ) { + global $wgFileExtensions, $wgLang; - $warning .= '
  • ' . wfMsgExt( 'fileexists', array('parseinline','replaceafter'), $dlink ) . '
  • ' . $dlink2; - - } elseif( $existsType == 'page-exists' ) { - $lnk = $sk->makeKnownLinkObj( $file->getTitle(), '', 'redirect=no' ); - $warning .= '
  • ' . wfMsgExt( 'filepageexists', array( 'parseinline', 'replaceafter' ), $lnk ) . '
  • '; - } elseif ( $existsType == 'exists-normalized' ) { - # Check if image with lowercase extension exists. - # It's not forbidden but in 99% it makes no sense to upload the same filename with uppercase extension - $dlink = $sk->linkKnown( $nt_lc ); - if ( $file_lc->allowInlineDisplay() ) { - // FIXME: replace deprecated makeImageLinkObj by link() - $dlink2 = $sk->makeImageLinkObj( $nt_lc, wfMsgExt( 'fileexists-thumb', 'parseinline' ), - $nt_lc->getText(), $align, array(), false, true ); - } elseif ( !$file_lc->allowInlineDisplay() && $file_lc->isSafeFile() ) { - $icon = $file_lc->iconThumb(); - $dlink2 = '
    ' . - $icon->toHtml( array( 'desc-link' => true ) ) . '
    ' . $dlink . '
    '; - } else { - $dlink2 = ''; - } + switch( $details['status'] ) { - $warning .= '
  • ' . - wfMsgExt( 'fileexists-extension', 'parsemag', - $file->getTitle()->getPrefixedText(), $dlink ) . - '
  • ' . $dlink2; + /** Statuses that only require name changing **/ + case UploadBase::MIN_LENGTH_PARTNAME: + $this->showRecoverableUploadError( wfMsgHtml( 'minlength1' ) ); + break; + case UploadBase::ILLEGAL_FILENAME: + $this->showRecoverableUploadError( wfMsgExt( 'illegalfilename', + 'parseinline', $details['filtered'] ) ); + break; + case UploadBase::OVERWRITE_EXISTING_FILE: + $this->showRecoverableUploadError( wfMsgExt( $details['overwrite'], + 'parseinline' ) ); + break; + case UploadBase::FILETYPE_MISSING: + $this->showRecoverableUploadError( wfMsgExt( 'filetype-missing', + 'parseinline' ) ); + break; - } elseif ( ( substr( $partname , 3, 3 ) == 'px-' || substr( $partname , 2, 3 ) == 'px-' ) - && preg_match( "/[0-9]{2}/" , substr( $partname , 0, 2 ) ) ) - { - # Check for filenames like 50px- or 180px-, these are mostly thumbnails - $nt_thb = Title::newFromText( substr( $partname , strpos( $partname , '-' ) +1 ) . '.' . $rawExtension ); - $file_thb = wfLocalFile( $nt_thb ); - if ($file_thb->exists() ) { - # Check if an image without leading '180px-' (or similiar) exists - $dlink = $sk->linkKnown( $nt_thb ); - if ( $file_thb->allowInlineDisplay() ) { - // FIXME: replace deprecated makeImageLinkObj by link() - $dlink2 = $sk->makeImageLinkObj( $nt_thb, - wfMsgExt( 'fileexists-thumb', 'parseinline' ), - $nt_thb->getText(), $align, array(), false, true ); - } elseif ( !$file_thb->allowInlineDisplay() && $file_thb->isSafeFile() ) { - $icon = $file_thb->iconThumb(); - $dlink2 = '
    ' . - $icon->toHtml( array( 'desc-link' => true ) ) . '
    ' . - $dlink . '
    '; + /** Statuses that require reuploading **/ + case UploadBase::EMPTY_FILE: + $this->showUploadError( wfMsgHtml( 'emptyfile' ) ); + break; + case UploadBase::FILE_TOO_LARGE: + $this->showUploadError( wfMsgHtml( 'largefileserver' ) ); + 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 ) ) + ) + ); + break; + case UploadBase::VERIFICATION_ERROR: + unset( $details['status'] ); + $code = array_shift( $details['details'] ); + $this->showUploadError( wfMsgExt( $code, 'parseinline', $details['details'] ) ); + break; + case UploadBase::HOOK_ABORTED: + if ( is_array( $details['error'] ) ) { # allow hooks to return error details in an array + $args = $details['error']; + $error = array_shift( $args ); } else { - $dlink2 = ''; + $error = $details['error']; + $args = null; } - $warning .= '
  • ' . wfMsgExt( 'fileexists-thumbnail-yes', 'parsemag', $dlink ) . - '
  • ' . $dlink2; - } else { - # Image w/o '180px-' does not exists, but we do not like these filenames - $warning .= '
  • ' . wfMsgExt( 'file-thumbnail-no', 'parseinline' , - substr( $partname , 0, strpos( $partname , '-' ) +1 ) ) . '
  • '; - } + $this->showUploadError( wfMsgExt( $error, 'parseinline', $args ) ); + break; + default: + throw new MWException( __METHOD__ . ": Unknown value `{$details['status']}`" ); } + } - $filenamePrefixBlacklist = UploadBase::getFilenamePrefixBlacklist(); - # Do the match - if(!isset($partname)) - $partname = ''; - foreach( $filenamePrefixBlacklist as $prefix ) { - if ( substr( $partname, 0, strlen( $prefix ) ) == $prefix ) { - $warning .= '
  • ' . wfMsgExt( 'filename-bad-prefix', 'parseinline', $prefix ) . '
  • '; - break; - } + /** + * Remove a temporarily kept file stashed by saveTempUploadedFile(). + * + * @return Boolean: success + */ + protected function unsaveUploadedFile() { + global $wgOut; + if ( !( $this->mUpload instanceof UploadFromStash ) ) { + return true; } + $success = $this->mUpload->unsaveUploadedFile(); + if ( !$success ) { + $wgOut->showFileDeleteError( $this->mUpload->getTempPath() ); + return false; + } else { + return true; + } + } + + /*** Functions for formatting warnings ***/ + + /** + * Formats a result of UploadBase::getExistsWarning as HTML + * This check is static and can be done pre-upload via AJAX + * + * @param $exists Array: the result of UploadBase::getExistsWarning + * @return String: empty string if there is no warning or an HTML fragment + */ + public static function getExistsWarning( $exists ) { + global $wgUser, $wgContLang; - if ( $file->wasDeleted() && !$file->exists() ) { + if ( !$exists ) { + return ''; + } + + $file = $exists['file']; + $filename = $file->getTitle()->getPrefixedText(); + $warning = ''; + + $sk = $wgUser->getSkin(); + + if( $exists['warning'] == 'exists' ) { + // Exact match + $warning = wfMsgExt( 'fileexists', 'parseinline', $filename ); + } elseif( $exists['warning'] == 'page-exists' ) { + // Page exists but file does not + $warning = wfMsgExt( 'filepageexists', 'parseinline', $filename ); + } elseif ( $exists['warning'] == 'exists-normalized' ) { + $warning = wfMsgExt( 'fileexists-extension', 'parseinline', $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', + $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 ); + } 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 - # Don't bother doing so if the file exists now, however $ltitle = SpecialPage::getTitleFor( 'Log' ); $llink = $sk->linkKnown( $ltitle, @@ -424,21 +651,22 @@ class UploadForm extends SpecialPage { array(), array( 'type' => 'delete', - 'page' => $file->getTitle()->getPrefixedText() + 'page' => $filename ) ); - $warning .= '
  • ' . wfMsgWikiHtml( 'filewasdeleted', $llink ) . '
  • '; + $warning = wfMsgWikiHtml( 'filewasdeleted', $llink ); } + return $warning; } /** * Get a list of warnings * - * @param string local filename, e.g. 'file exists', 'non-descriptive filename' - * @return array list of warning messages + * @param $filename String: local filename, e.g. 'file exists', 'non-descriptive filename' + * @return Array: list of warning messages */ - static function ajaxGetExistsWarning( $filename ) { + public static function ajaxGetExistsWarning( $filename ) { $file = wfFindFile( $filename ); if( !$file ) { // Force local file so we have an object to do further checks against @@ -449,48 +677,28 @@ class UploadForm extends SpecialPage { if ( $file ) { $exists = UploadBase::getExistsWarning( $file ); $warning = self::getExistsWarning( $exists ); - // FIXME: We probably also want the prefix blacklist and the wasdeleted check here if ( $warning !== '' ) { - $s = ""; + $s = "
    $warning
    "; } } return $s; } /** - * Render a preview of a given license for the AJAX preview on upload - * - * @param string $license - * @return string - */ - public static function ajaxGetLicensePreview( $license ) { - global $wgParser, $wgUser; - $text = '{{' . $license . '}}'; - $title = Title::makeTitle( NS_FILE, 'Sample.jpg' ); - $options = ParserOptions::newFromUser( $wgUser ); - - // Expand subst: first, then live templates... - $text = $wgParser->preSaveTransform( $text, $title, $wgUser, $options ); - $output = $wgParser->parse( $text, $title, $options ); - - return $output->getText(); - } - - /** - * Construct the human readable warning message from an array of duplicate files + * Construct a warning and a gallery from an array of duplicate files. */ public static function getDupeWarning( $dupes ) { if( $dupes ) { global $wgOut; - $msg = ""; + $msg = ''; foreach( $dupes as $file ) { $title = $file->getTitle(); $msg .= $title->getPrefixedText() . - "|" . $title->getText() . "\n"; + '|' . $title->getText() . "\n"; } - $msg .= ""; - return "
  • " . - wfMsgExt( "file-exists-duplicate", array( "parse" ), count( $dupes ) ) . + $msg .= ''; + return '
  • ' . + wfMsgExt( 'file-exists-duplicate', array( 'parse' ), count( $dupes ) ) . $wgOut->parse( $msg ) . "
  • \n"; } else { @@ -498,604 +706,373 @@ class UploadForm extends SpecialPage { } } +} - /** - * Remove a temporarily kept file stashed by saveTempUploadedFile(). - * @access private - * @return success - */ - function unsaveUploadedFile() { - global $wgOut; - $success = $this->mUpload->unsaveUploadedFile(); - if ( ! $success ) { - $wgOut->showFileDeleteError( $this->mUpload->getTempPath() ); - return false; - } else { - return true; +/** + * 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 $mTextTop; + protected $mTextAfterSummary; + + protected $mSourceIds; + + public function __construct( $options = array() ) { + $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']; + + $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' ); + # Used message keys: 'accesskey-upload', 'tooltip-upload' + $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']; + } } - } - /* Interface code starts below this line * - * -------------------------------------------------------------- */ - - - /** - * @param string $error as HTML - * @access private - */ - function uploadError( $error ) { - global $wgOut; - $wgOut->addHTML( '

    ' . wfMsgHtml( 'uploadwarning' ) . "

    \n" ); - $wgOut->addHTML( '' . $error . '' ); } /** - * There's something wrong with this file, not enough to reject it - * totally but we require manual intervention to save it for real. - * Stash it away, then present a form asking to confirm or cancel. + * Get the descriptor of the fieldset that contains the file source + * selection. The section is 'source' * - * @param string $warning as HTML - * @access private + * @return Array: descriptor array */ - function uploadWarning( $warnings ) { - global $wgOut, $wgUser; - global $wgUseCopyrightUpload; - - $this->mSessionKey = $this->mUpload->stashSession(); - - if( $sessionData === false ) { - # Couldn't save file; an error has been displayed so let's go. - return; + protected function getSourceSection() { + global $wgLang, $wgUser, $wgRequest; + global $wgMaxUploadSize; + + if ( $this->mSessionKey ) { + return array( + 'wpSessionKey' => array( + 'type' => 'hidden', + 'default' => $this->mSessionKey, + ), + 'wpSourceType' => array( + 'type' => 'hidden', + 'default' => 'Stash', + ), + ); } - $sk = $wgUser->getSkin(); - - $wgOut->addHTML( '

    ' . wfMsgHtml( 'uploadwarning' ) . "

    \n" ); - $wgOut->addHTML( '