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 .= '