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; } if ( $this->chunkMode == self::CHUNK || $this->chunkMode == self::DONE ) { $this->mTempPath = $path; $this->fileSize += $fileSize; } } /** * 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->sessionKey; } /** * 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->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 = Status::newFromFatal( 'Missing session data.' ); } } /** * 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->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 $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( $comment, $pageText, $watch ) ) ); $wgOut->disable(); } else if ( $this->chunkMode == self::CHUNK ) { $status = $this->appendChunk(); if ( !$status->isOK() ) { return $status; } // 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 // http://www.firefogg.org/dev/chunk_post.html ob_clean(); echo FormatJson::encode( array( 'result' => 1, 'done' => 1, 'resultUrl' => $file->getDescriptionUrl() ) ); $wgOut->disable(); } } /** * 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 ( !$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 { $this->status = Status::newFatal( 'filenotfound', $this->repoPath ); } if ( $this->fileSize > $wgMaxUploadSize ) $this->status = Status::newFatal( 'largefileserver' ); } 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 ); } }