* Fix for r57997 and bug 21222: move math, gallery, pre and nowiki to a new module...
[lhc/web/wiklou.git] / includes / upload / UploadFromChunks.php
index 1167243..c0a8912 100644 (file)
  * @file
  * @ingroup upload
  *
- * @author Michael Dale
+ * First, destination checks are made, and, if ignorewarnings is not
+ * checked, errors / warning is returned.
  *
- * first destination checks are made (if ignorewarnings is not checked) errors / warning is returned.
+ * 1. We return the uploadUrl.
+ * 2. We then accept chunk uploads from the client.
+ * 3. Return chunk id on each POSTED chunk.
+ * 4. Once the client posts "done=1", the files are concatenated together.
  *
- * we return the uploadUrl
- * we then accept chunk uploads from the client.
- * return chunk id on each POSTED chunk
- * once the client posts done=1 concatenated the files together.
- * more info at: http://firefogg.org/dev/chunk_post.html
+ * (More info at: http://firefogg.org/dev/chunk_post.html)
  */
 class UploadFromChunks extends UploadBase {
 
-       var $chunk_mode; // init, chunk, done
-       var $mSessionKey = false;
-       var $status = array();
-
-       const INIT      = 1;
+       const INIT = 1;
        const CHUNK = 2;
-       const DONE      = 3;
-       public function initializeFromRequest( &$request ){
-               //should merge initializeFromParams (but just needs to be working atm)
-       }
-       public function initializeFromParams( $param, &$request ) {
-               $this->initFromSessionKey( $param['chunksessionkey'], $request );
-               // set the chunk mode:
-               if( !$this->mSessionKey && !$param['done'] ){
-                       // session key not set init the chunk upload system:
-                       $this->chunk_mode = UploadFromChunks::INIT;
-                       $this->mDesiredDestName = $param['filename'];
-               } else if( $this->mSessionKey && !$param['done'] ){
-                       // this is a chunk piece
-                       $this->chunk_mode = UploadFromChunks::CHUNK;
-               } else if( $this->mSessionKey && $param['done'] ){
-                       // this is the last chunk
-                       $this->chunk_mode = UploadFromChunks::DONE;
-               }
-               if( $this->chunk_mode == UploadFromChunks::CHUNK ||
-                       $this->chunk_mode == UploadFromChunks::DONE ){
-                               // set chunk related vars:
-                               $this->mTempPath = $request->getFileTempName( 'chunk' );
-                               $this->mFileSize = $request->getFileSize( 'chunk' );
-               }
-
-               return $this->status;
-       }
-       static function isValidRequest( $request ) {
-               $sessionData = $request->getSessionData( 'wsUploadData' );
-               if( !self::isValidSessionKey(
-                       $request->getInt( 'wpSessionKey' ),
-                       $sessionData ) )
-                               return false;
-               // check for the file:
-               return (bool)$request->getFileTempName( 'file' );
+       const DONE = 3;
+
+       protected $chunkMode; // INIT, CHUNK, DONE
+       protected $sessionKey;
+       protected $comment;
+       protected $fileSize = 0;
+       protected $repoPath;
+       protected $pageText;
+       protected $watch;
+
+       public $status;
+
+       // Parent class requires this function even though it is only
+       // used from SpecialUpload.php and we don't do chunked uploading
+       // from SpecialUpload -- best to raise an exception for
+       // now.
+       public function initializeFromRequest( &$request ) {
+               throw new MWException( 'not implemented' );
        }
 
-       /* check warnings depending on chunk_mode */
-       function checkWarnings(){
-               $warning = array();
-               return $warning;
-       }
-
-       function isEmptyFile(){
-               // does not apply to chunk init
-               if( $this->chunk_mode == UploadFromChunks::INIT ){
-                       return false;
-               } else {
-                       return parent::isEmptyFile();
+       public function initialize( $done, $filename, $sessionKey, $path,
+               $fileSize, $sessionData )
+       {
+               $this->initFromSessionKey( $sessionKey, $sessionData );
+
+               if ( !$this->sessionKey && !$done ) {
+                       // session key not set, init the chunk upload system:
+                       $this->chunkMode = self::INIT;
+                       $this->mDesiredDestName = $filename;
+               } else if ( $this->sessionKey && !$done ) {
+                       $this->chunkMode = self::CHUNK;
+               } else if ( $this->sessionKey && $done ) {
+                       $this->chunkMode = self::DONE;
                }
-       }
-
-       /**
-        * Verify whether the upload is sane.
-        * Returns self::OK or else an array with error information
-        */
-       function verifyUpload() {
-               // no checks on chunk upload mode:
-               if( $this->chunk_mode ==  UploadFromChunks::INIT )
-                       return array( 'status' => self::OK );
-
-               // verify on init and last chunk request
-               if(     $this->chunk_mode == UploadFromChunks::CHUNK ||
-                       $this->chunk_mode == UploadFromChunks::DONE )
-                       return parent::verifyUpload();
-       }
 
-       // only run verifyFile on completed uploaded chunks
-       function verifyFile(){
-               if( $this->chunk_mode == UploadFromChunks::DONE ){
-                       // first append last chunk (so we can do a real verifyFile check... (check file type etc)
-                       $status = $this->doChunkAppend();
-                       if( $status->isOK() ){
-                               $this->mTempPath = $this->getRealPath( $this->mTempAppendPath );
-                               // verify the completed merged chunks as if it was the file that got uploaded:
-                               return parent::verifyFile( $this->mTempPath );
-                       } else {
-                               // conflict of status returns (have to return the error ary) ... why we don't consistantly use a status object is beyond me..
-                               return $status->getErrorsArray();
-                       }
-               } else {
-                       return true;
+               if ( $this->chunkMode == self::CHUNK || $this->chunkMode == self::DONE ) {
+                       $this->mTempPath = $path;
+                       $this->fileSize += $fileSize;
                }
        }
 
-       // pretty ugly inter-mixing of mParam and local vars
-       function setupChunkSession( $summary, $comment, $watch ) {
-               $this->mSessionKey = $this->getSessionKey();
-               $_SESSION['wsUploadData'][$this->mSessionKey] = array(
-                       'mComment'                      => $comment,
-                       'mSummary'                      => $summary,
-                       'mWatch'                        => $watch,
-                       'mIgnorewarnings'       => true, //ignore warning on chunk uploads (for now)
-                       'mFilteredName'         => $this->mFilteredName,
-                       'mTempAppendPath'       => null, // the repo append path (not temporary local node mTempPath)
-                       'mDesiredDestName'      => $this->mDesiredDestName,
-                       'version'                       => self::SESSION_VERSION,
+       /**
+        * Set session information for chunked uploads and allocate a unique key.
+        * @param $comment string
+        * @param $pageText string
+        * @param $watch boolean
+        *
+        * @returns string the session key for this chunked upload
+        */
+       protected function setupChunkSession( $comment, $pageText, $watch ) {
+               $this->sessionKey = $this->getSessionKey();
+               $_SESSION['wsUploadData'][$this->sessionKey] = array(
+                       'comment' => $comment,
+                       'pageText' => $pageText,
+                       'watch' => $watch,
+                       'mFilteredName' => $this->mFilteredName,
+                       'repoPath' => null,
+                       'mDesiredDestName' => $this->mDesiredDestName,
+                       'version' => self::SESSION_VERSION,
                );
-               return $this->mSessionKey;
+               return $this->sessionKey;
        }
 
-       function initFromSessionKey( $sessionKey, $request ){
-               if( !$sessionKey || empty( $sessionKey ) ){
-                       return false;
+       /**
+        * Initialize a continuation of a chunked upload from a session key
+        * @param $sessionKey string
+        * @param $request WebRequest
+        *
+        * @returns void
+        */
+       protected function initFromSessionKey( $sessionKey, $sessionData ) {
+               // testing against null because we don't want to cause obscure
+               // bugs when $sessionKey is full of "0"
+               if ( $sessionKey !== null ) {
+                       $this->status = Status::newFromFatal( 'import-token-mismatch' );
+                       return;
                }
-               $this->mSessionKey = $sessionKey;
-               // load the sessionData array:
-               $sessionData = $request->getSessionData( 'wsUploadData' );
-
-               if( isset( $sessionData[$this->mSessionKey]['version'] ) &&
-                       $sessionData[$this->mSessionKey]['version'] == self::SESSION_VERSION ) {
-                       // update the local object from the session
-                       $this->mComment          = $sessionData[$this->mSessionKey]['mComment'];
-                       $this->mSummary          = $sessionData[$this->mSessionKey]['mSummary'];
-                       $this->mWatch            = $sessionData[$this->mSessionKey]['mWatch'];
-                       $this->mIgnorewarnings   = $sessionData[$this->mSessionKey]['mIgnorewarnings'];
-                       $this->mFilteredName     = $sessionData[$this->mSessionKey]['mFilteredName'];
-                       $this->mTempAppendPath   = $sessionData[$this->mSessionKey]['mTempAppendPath'];
-                       $this->mDesiredDestName  = $sessionData[$this->mSessionKey]['mDesiredDestName'];
+               $this->sessionKey = $sessionKey;
+
+               if ( isset( $sessionData[$this->sessionKey]['version'] )
+                       && $sessionData[$this->sessionKey]['version'] == self::SESSION_VERSION )
+               {
+                       $this->comment = $sessionData[$this->sessionKey]['comment'];
+                       $this->pageText = $sessionData[$this->sessionKey]['pageText'];
+                       $this->watch = $sessionData[$this->sessionKey]['watch'];
+                       $this->mFilteredName = $sessionData[$this->sessionKey]['mFilteredName'];
+                       $this->repoPath = $sessionData[$this->sessionKey]['repoPath'];
+                       $this->mDesiredDestName = $sessionData[$this->sessionKey]['mDesiredDestName'];
                } else {
-                       $this->status = array( 'error' => 'missing session data' );
-                       return false;
+                       $this->status = Status::newFromFatal( 'Missing session data.' );
                }
        }
 
-       // Lets us return an api result (as flow for chunk uploads is kind of different than others.
-       function performUpload( $summary = '', $comment = '', $watch = '', $user ){
-               global $wgUser;
+       /**
+        * Handle a chunk of the upload.  Overrides the parent method
+        * because Chunked Uploading clients (i.e. Firefogg) require
+        * specific API responses.
+        * @see UploadBase::performUpload
+        */
+       public function performUpload( $comment, $pageText, $watch, $user ) {
+               wfDebug( "\n\n\performUpload(chunked): comment:" . $comment . ' pageText: ' . $pageText . ' watch:' . $watch );
+               global $wgUser, $wgOut;
 
-               if( $this->chunk_mode == UploadFromChunks::INIT ){
+               if ( $this->chunkMode == self::INIT ) {
                        // firefogg expects a specific result per:
                        // http://www.firefogg.org/dev/chunk_post.html
 
                        // it's okay to return the token here because
                        // a) the user must have requested the token to get here and
                        // b) should only happen over POST
-                       // c) (we need the token to validate chunks are coming from a non-xss request)
+                       // c) we need the token to validate chunks are coming from a non-xss request
                        $token = urlencode( $wgUser->editToken() );
                        ob_clean();
                        echo FormatJson::encode( array(
-                                       'uploadUrl' => wfExpandUrl( wfScript( 'api' ) ) . "?action=upload&".
-                                                                       "token={$token}&format=json&enablechunks=true&chunksessionkey=".
-                                                                       $this->setupChunkSession( $summary, $comment, $watch ) ) );
-                       exit( 0 );
-               } else if( $this->chunk_mode == UploadFromChunks::CHUNK ){
-                       $status = $this->doChunkAppend();
-                       if( $status->isOK() ){
-                               // return success:
-                               // firefogg expects a specific result per:
-                               // http://www.firefogg.org/dev/chunk_post.html
-                               ob_clean();
-                               echo FormatJson::encode( array(
-                                               'result' => 1,
-                                               'filesize' => filesize( $this->getRealPath( $this->mTempAppendPath ) )
-                                       )
-                               );
-                               exit( 0 );
-                               /*return array(
-                                       'result' => 1
-                               );*/
-                       } else {
+                               'uploadUrl' => wfExpandUrl( wfScript( 'api' ) ) . "?action=upload&" .
+                               "token={$token}&format=json&enablechunks=true&chunksessionkey=" .
+                               $this->setupChunkSession( $comment, $pageText, $watch ) ) );
+                       $wgOut->disable();
+               } else if ( $this->chunkMode == self::CHUNK ) {
+                       $status = $this->appendChunk();
+                       if ( !$status->isOK() ) {
                                return $status;
                        }
-               } else if( $this->chunk_mode == UploadFromChunks::DONE ){
-                       // update the values from the local (session init) if not paseed again)
-                       if( $summary == '' )
-                               $summary = $this->mSummary;
-
-                       if( $comment == '' )
-                               $comment = $this->mComment;
-
-                       if( $watch == '' )
-                               $watch = $this->mWatch;
-                       $status = parent::performUpload( $summary, $comment, $watch, $user );
-                       if( !$status->isGood() ) {
+                       // return success:
+                       // firefogg expects a specific result
+                       // http://www.firefogg.org/dev/chunk_post.html
+                       ob_clean();
+                       echo FormatJson::encode(
+                               array( 'result' => 1, 'filesize' => $this->fileSize )
+                       );
+                       $wgOut->disable();
+               } else if ( $this->chunkMode == self::DONE ) {
+                       if ( !$comment )
+                               $comment = $this->comment;
+
+                       if ( !$pageText )
+                               $pageText = $this->pageText;
+
+                       if ( !$watch )
+                               $watch = $this->watch;
+
+                       $status = parent::performUpload( $comment, $pageText, $watch, $user );
+                       if ( !$status->isGood() ) {
                                return $status;
                        }
                        $file = $this->getLocalFile();
-                       // firefogg expects a specific result per:
+
+                       // firefogg expects a specific result
                        // http://www.firefogg.org/dev/chunk_post.html
                        ob_clean();
                        echo FormatJson::encode( array(
-                                       'result' => 1,
-                                       'done' => 1,
-                                       'resultUrl' => $file->getDescriptionUrl()
-                               )
+                               'result' => 1,
+                               'done' => 1,
+                               'resultUrl' => $file->getDescriptionUrl() )
                        );
-                       exit( 0 );
-
+                       $wgOut->disable();
                }
        }
 
-       // append the given chunk to the temporary uploaded file. (if no temporary uploaded file exists created it.
-       function doChunkAppend(){
+       /**
+        * Append a chunk to the Repo file
+        *
+        * @param string $srcPath Path to file to append from
+        * @param string $toAppendPath Path to file to append to
+        * @return Status Status
+        */
+       protected function appendToUploadFile( $srcPath, $toAppendPath ) {
+               $repo = RepoGroup::singleton()->getLocalRepo();
+               $status = $repo->append( $srcPath, $toAppendPath );
+               return $status;
+       }
+
+       /**
+        * Append a chunk to the temporary file.
+        *
+        * @return void
+        */
+       protected function appendChunk() {
                global $wgMaxUploadSize;
-               // if we don't have a mTempAppendPath to generate a file from the chunk packaged var:
-               if( !$this->mTempAppendPath ){
-                       // get temp name:
-                       // make a chunk store path. (append tmp file to chunk)
-                       $status = $this->saveTempUploadedFile( $this->mDestName, $this->mTempPath );
-
-                       if( $status->isOK() ) {
-                               $this->mTempAppendPath = $status->value;
-                               $_SESSION['wsUploadData'][$this->mSessionKey]['mTempAppendPath'] = $this->mTempAppendPath;
+
+               if ( !$this->repoPath ) {
+                       $this->status = $this->saveTempUploadedFile( $this->mDesiredDestName, $this->mTempPath );
+
+                       if ( $status->isOK() ) {
+                               $this->repoPath = $status->value;
+                               $_SESSION['wsUploadData'][$this->sessionKey]['repoPath'] = $this->repoPath;
                        }
                        return $status;
+               }
+               if ( $this->getRealPath( $this->repoPath ) ) {
+                       $this->status = $this->appendToUploadFile( $this->repoPath, $this->mTempPath );
                } else {
-                       if( is_file( $this->getRealPath( $this->mTempAppendPath ) ) ){
-                               $status = $this->appendToUploadFile( $this->mTempAppendPath, $this->mTempPath );
-                       } else {
-                               $status = Status::newFatal( 'filenotfound', $this->mTempAppendPath );
-                       }
-                       //check to make sure we have not expanded beyond $wgMaxUploadSize
-                       if( filesize(  $this->getRealPath( $this->mTempAppendPath ) ) >  $wgMaxUploadSize )
-                               $status = Status::newFatal( 'largefileserver' );
+                       $this->status = Status::newFatal( 'filenotfound', $this->repoPath );
+               }
+               if ( $this->fileSize >  $wgMaxUploadSize )
+                       $this->status = Status::newFatal( 'largefileserver' );
+       }
 
-                       return $status;
+       public function verifyUpload() {
+               if ( $this->chunkMode != self::DONE ) {
+                       return Status::newGood();
                }
+               return parent::verifyUpload();
        }
 
+       public function checkWarnings() {
+               if ( $this->chunkMode != self::DONE ) {
+                       return null;
+               }
+               return parent::checkWarnings();
+       }
+
+       public function getImageInfo( $result ) {
+               if ( $this->chunkMode != self::DONE ) {
+                       return null;
+               }
+               return parent::getImageInfo( $result );
+       }
 }