getRequest(); $this->checkToken( $request ); $this->mFrom = 1; # $request->getVal( 'from', 1 ); global $wgMultiUploadInitialNumberOfImportRows; $this->mTo = $request->getVal( 'wpLastRowIndex', $wgMultiUploadInitialNumberOfImportRows ); # stick an invisible template row in front $this->mRows = array( $this->createRow( 'template' ), ); $i = $this->mFrom; while ( $i <= $this->mTo ) { $this->mRows[] = $row = $this->createRow( $i ); $row->handleRequestData(); $i++; } $this->showUploadForm( $this->getUploadForm() ); } protected function createRow( $i ) { $row = new UploadRow( $this, $i ); $row->setContext( $this->getContext() ); return $row; } /** * 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 true if warning's already been dealt with * @return UploadForm */ protected function getUploadForm( $message = '', $sessionKey = '', $hideIgnoreWarning = false ) { # Initialize form $form = new MultiUploadForm( $this, $this->mRows, $this->mTo, $this->getContext() ); $form->setTitle( $this->getTitle() ); # Check the edit token. # Unlike Special:Upload, no fine distinctions about # whether they're uploading vs. cancelling, etc. if ( !$this->mTokenOk && $this->getRequest()->wasPosted() ) { $form->addPreText( $this->msg( 'session_fail_preview' )->parse() ); } # Add the page-top text. $form->addPreText( $this->msg( 'multiupload-text' )->parse() ); // @todo FIXME: add footer return $form; } public function getGlobalFormDescriptors() { return array(); } } /** * Subclass of HTMLForm that provides the form section of Special:MultiUpload */ class MultiUploadForm extends UploadForm { protected $mPage; protected $mRows; protected $mLastIndex; public function __construct( $page, $rows, $lastIndex, IContextSource $context = null ) { $this->mPage = $page; $this->mRows = $rows; $this->mLastIndex = $lastIndex; parent::__construct( array(), $context ); $this->mSourceIds = array(); $this->mMessagePrefix = 'multiupload'; $this->setSubmitText( wfMessage( 'multiupload-submit' )->parse() ); } protected function constructData( array $options = array(), IContextSource $context = null ) { } protected function constructForm( IContextSource $context ) { $descriptor = $this->getGlobalFormDescriptors() + $this->mPage->getGlobalFormDescriptors(); foreach ( $this->mRows as $row ) { $rowdesc = $row->getFormDescriptors(); $descriptor = $descriptor + $rowdesc; } HTMLForm::__construct( $descriptor, $context, 'upload' ); } protected function getGlobalFormDescriptors() { return array( 'LastRowIndex' => array( 'type' => 'hidden', 'id' => 'wpLastRowIndex', 'default' => $this->mLastIndex, ), ); } public function getLegend( $key ) { $parts = explode( '-', $key ); $msg = array_shift( $parts ); return wfMessage( "{$this->mMessagePrefix}-$msg", $parts )->parse(); } protected function addJsConfigVars( $out ) { parent::addJsConfigVars( $out ); $jsConfig = array( 'wpFirstRowIndex' => $this->mPage->mFrom, 'wpLastRowIndex' => $this->mPage->mTo, 'wgMultiUploadMaxPhpUploadSize' => min( wfShorthandToInteger( ini_get( 'upload_max_filesize' ) ), wfShorthandToInteger( ini_get( 'post_max_size' ) ) ), ); foreach ( $this->mRows as $row ) { $jsConfig = $jsConfig + $row->jsConfigVars(); } $out->addJsConfigVars( $jsConfig ); } protected function addRLModules( $out ) { $out->addModules( array( 'ext.multiupload.top', 'ext.multiupload', ) ); } } /** * Hoping this gets merged into core, won't have to do it here */ if ( !class_exists( 'FauxWebRequestUpload' ) ) { /** * A WebRequestUpload that can be faked. */ class FauxWebRequestUpload extends WebRequestUpload { /** * Constructor. Should only be called by FauxRequest. * * @param $request WebRequest The associated request * @param array $data Data in the same format that would be found * in the $_FILES array. If provided, will be used * instead of $_FILES[$key]. */ public function __construct( $request, $data ) { $this->request = $request; $this->fileInfo = $data; $this->doesExist = true; } } /** * allow DerivativeRequest to include fake uploaded files */ class DerivativeRequestWithFiles extends DerivativeRequest { /** * @param string $key * @return FauxWebRequestUpload|WebRequestUpload */ public function getUpload( $key ) { if ( array_key_exists( $key, $this->data ) ) { return new FauxWebRequestUpload( $this, $this->data[$key] ); } else { return new WebRequestUpload( $this, $key ); } } } } else { /** * If the feature is in MW core, just use it */ class DerivativeRequestWithFiles extends DerivativeRequest { } } class UploadRow extends SpecialUpload { public $mPage; public $mRowNumber; public $mRequest; public $mFormMessage; public $mSessionKey; public $mHideIgnoreWarning; public $mExtraButtons; /** * Different constructor, let it know which row it is and * the upload object it belongs to */ public function __construct( $page, $number ) { $this->mPage = $page; $this->setContext( $page->getContext() ); $this->mRowNumber = $number; $this->mRequest = null; $this->mFormMessage = ''; $this->mSessionKey = ''; $this->mHideIgnoreWarning = ''; $this->mExtraButtons = array(); } /** * UploadBase and various parent class methods expect certain * form field names that don't have a row number appended. * Here we create a fake request object that responds to those field names. * * @return DerivativeRequestWithFiles */ public function getRequest() { if ( !$this->mRequest ) { $webRequest = $this->mPage->getRequest(); $i = $this->mRowNumber; $valuesKept = $valuesAltered = array(); foreach ( $webRequest->getValues() + $_FILES as $key => $value ) { $matches = null; $prefixMatch = preg_match( '/^(.*?)(\d+)$/', $key, $matches ); if ( $prefixMatch === false ) { // ERROR } elseif ( $prefixMatch == 0 ) { // key has no row number $valuesKept[$key] = $value; } elseif ( $matches[2] == $this->mRowNumber ) { // key has my row number $valuesAltered[$matches[1]] = $value; } // else it has some other row number } $this->mRequest = new DerivativeRequestWithFiles( $webRequest, $valuesKept + $valuesAltered, $webRequest->wasPosted() ); } return $this->mRequest; } protected function handleRequestData() { $request = $this->getRequest(); $this->mUploadSuccessful = $request->getCheck( 'wpUploadSuccessful' ); parent::handleRequestData(); } /** * We don't do our own form output - we give all output to the * page object to aggregate into a single form */ protected function showUploadForm( $form ) { // it gets called // wfDebug(" *** SHOWUPLOADFORM SHOULD NOT BE CALLED! *** \n"); } /** * Unlike the superclass, don't actually create a form object when * this is called, wait and do it when the page object is ready to * assemble its full output form. * * @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 * todo lots of redundancy here */ public function getUploadForm( $message = '', $sessionKey = '', $hideIgnoreWarning = false ) { } public function showUploadError( $message ) { $this->mFormMessage .= $this->getUploadError( $message ); } protected function showRecoverableUploadError( $message ) { $this->mSessionKey = $this->mUpload->stashSession(); $this->mFormMessage .= $this->getRecoverableUploadError( $message ); } protected function showUploadWarning( $warnings ) { $warningHtml = $this->getUploadWarning( $warnings ); if ( $warningHtml === false ) { return false; } $this->mSessionKey = $this->mUpload->stashSession(); $this->mFormMessage .= $warningHtml; $this->mHideIgnoreWarning = true; # Special:Upload changes the 'Upload' button to # 'Submit modified file description', and adds two # additional submit buttons. We add the additional # two as check boxes, and just leave the # 'Upload' button below all rows. $this->mExtraButtons = array( 'UploadIgnoreWarning' => 'ignorewarning', 'CancelUpload' => 'reuploaddesc', ); return true; } /** * This is apparently a pretty bad one. * Special:Upload replaces the whole page with an error page * when this happens. I'll just do it as an error message added * to the form. But if it happens, you should probably start * over clean. */ protected function showFileDeleteError() { $this->mFormMessage .= '
' . $this->getOutput()->msg( 'filenotfound', $this->mUpload->getTempPath() ) . '
'; } /** * Suppress error message that file is empty, because this * happens normally when you don't fill all the rows of the form. */ protected function processVerificationError( $details ) { if ( $details['status'] === UploadBase::EMPTY_FILE && $this->mDesiredDestName === '' ) { return; } parent::processVerificationError( $details ); } protected function createFormRow() { return new UploadFormRow( $this, $this->getFormOptions( $this->mSessionKey, $this->mHideIgnoreWarning ), $this->getContext() ); } public function getFormDescriptors() { # Initialize form $form = $this->createFormRow(); $preText = ''; if ( $this->mDesiredDestName ) { $preText .= $this->getViewDeletedLinks(); } # 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::makeTitleSafe( NS_FILE, $this->mDesiredDestName ); $delNotice = ''; // empty by default if ( $desiredTitleObj instanceof Title && !$desiredTitleObj->exists() ) { LogEventsList::showLogExtract( $delNotice, array( 'delete', 'move' ), $desiredTitleObj, '', array( 'lim' => 10, 'conds' => array( "log_action != 'revision'" ), 'showIfEmpty' => false, 'msgKey' => array( 'upload-recreate-warning' ) ) ); } $preText .= $delNotice; $preText .= $this->mFormMessage; return $form->descriptor( $preText, $this->mExtraButtons, $this->mUploadSuccessful ); } protected function shouldProcessUpload() { return ( !$this->mUploadSuccessful && $this->mPage->mTokenOk && !$this->mCancelUpload && ( $this->getRequest()->getVal( 'wpDestFile' ) && $this->mUploadClicked ) ); } protected function uploadSucceeded() { $this->mDesiredDestName = $this->mLocalFile->getTitle()->getDBkey(); } /** * @return array */ public function jsConfigVars() { return array( 'wgMultiUploadAutoFill' . $this->mRowNumber => ( !$this->mForReUpload && // if mDestFile was provided in the request, // don't overwrite it by autofilling $this->mDesiredDestName === '' ), ); } } class UploadFormRow extends UploadForm { public $mRow; function __construct( $row, array $options = array(), IContextSource $context = null ) { $this->mRow = $row; $this->constructData( $options, $context ); # HTMLForm::__construct( array(), $context, 'upload' ); # $this->mSourceIds = array(); } protected function twoColumnDescriptor( $text, $section ) { return array( 'type' => 'info', 'raw' => true, 'rawrow' => true, 'default' => '' . $text . '', 'section' => $section, ); } protected function uploadedMessage() { $destTitle = Title::newFromText( $this->mDestFile, NS_FILE ); return '
' . wfMessage( 'multiupload-uploadedto', Linker::linkKnown( $destTitle, $destTitle->getText() ) )->text() . '
'; } protected function uploadSucceededDescriptor( $i, $sectionLabel ) { return array( 'UploadedMessage' . $i => $this->twoColumnDescriptor( $this->uploadedMessage(), $sectionLabel ), 'DestFile' . $i => array( 'type' => 'hidden', 'default' => $this->mDestFile, 'section' => $sectionLabel ), 'UploadSuccessful' . $i => array( 'type' => 'hidden', 'default' => true, 'section' => $sectionLabel ), ); } public function descriptor( $preText = '', $extraButtons = array(), $uploadSuccessful = false ) { $descriptor = array(); $i = $this->mRow->mRowNumber; $sectionLabel = 'row-' . $i; if ( $uploadSuccessful ) { return $this->uploadSucceededDescriptor( $i, $sectionLabel ); } $sectionDescriptors = $this->getSourceSection() + $this->getDescriptionSection() + $this->getOptionsSection(); $header = ''; foreach ( array( $preText, $this->mHeader ) + $this->mSectionHeaders as $head ) { if ( $head != '' ) { if ( $header != '' ) { $header .= "
\n"; } $header .= $head; } } $preTextSection = array(); if ( $header != '' ) { $preTextSection['Message'] = $this->twoColumnDescriptor( $header, $sectionLabel ); } # a couple markers for the JavaScript animations if ( isset( $sectionDescriptors['DestFile'] ) ) { $sectionDescriptors['DestFile']['cssclass'] = 'multiupload-first-to-collapse multiupload-width-exemplar'; } foreach ( $preTextSection + $sectionDescriptors as $name => $field ) { if ( isset( $field['id'] ) ) { # put the IDs that Special:Upload uses into # the class attributes, without numbers appended, # so that JavaScript routines can find them that # way if ( isset( $field['cssclass'] ) ) { $field['cssclass'] .= ' ' . $field['id']; } else { $field['cssclass'] = $field['id']; } # add the row number to the actual ID, for use # as distinct form fields. $field['id'] = $field['id'] . $i; } if ( isset( $field['radio-name'] ) ) { $field['radio-name'] = $field['radio-name'] . $i; } if ( isset( $field['section'] ) ) { if ( isset( $field['cssclass'] ) ) { $field['cssclass'] .= ' '; } else { $field['cssclass'] = ''; } $field['cssclass'] .= 'mw-htmlform-section-' . str_replace( '/', '-', $field['section'] ); } $field['section'] = $sectionLabel; $descriptor["$name$i"] = $field; } foreach ( $extraButtons as $key => $msg ) { $descriptor[$key] = array( 'type' => 'check', 'id' => $key, 'label-message' => $msg, 'section' => $sectionLabel, ); } return $descriptor; } }