wiki.techinc.nl/includes/upload/UploadFromChunks.php
Mark A. Hershberger c74fe71cd8 * new FauxResponse class to help with unit testing
* Add append() method to FileRepo classes to enable chunked uploading
* Change chunksessionkey to chunksession
* Remove echo json stuff
* Fix a multitude of bugs in my own code
* still to test: mwEmbed use of chunked upload
2010-02-10 10:36:11 +00:00

233 lines
7 KiB
PHP

<?php
/**
* @file
* @ingroup upload
*
* First, destination checks are made, and, 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.
*
* (More info at: http://firefogg.org/dev/chunk_post.html)
*/
class UploadFromChunks extends UploadBase {
const INIT = 1;
const CHUNK = 2;
const DONE = 3;
protected $chunkMode; // INIT, CHUNK, DONE
protected $sessionKey;
protected $comment;
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' );
}
public function initialize( $done, $filename, $sessionKey, $path, $fileSize, $sessionData ) {
global $wgTmpDirectory;
$this->status = new Status;
$this->initFromSessionKey( $sessionKey, $sessionData );
if ( !$this->sessionKey && !$done ) {
// session key not set, init the chunk upload system:
$this->chunkMode = self::INIT;
$this->initializePathInfo( $filename, $path, 0, true);
} 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->mFileSize += $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 ) {
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::newFatal( 'invalid-session-key' );
}
}
/**
* 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
return Status::newGood(
array('uploadUrl' => wfExpandUrl( wfScript( 'api' ) ) . "?" .
wfArrayToCGI(array('action' => 'upload',
'token' => $wgUser->editToken(),
'format' => 'json',
'filename' => $pageText,
'enablechunks' => 'true',
'chunksession' => $this->setupChunkSession( $comment, $pageText, $watch ) ) ) ) );
} else if ( $this->chunkMode == self::CHUNK ) {
$this->appendChunk();
if ( !$this->status->isOK() ) {
return $this->status;
}
// return success:
// firefogg expects a specific result
// http://www.firefogg.org/dev/chunk_post.html
return Status::newGood(
array( 'result' => 1, 'filesize' => $this->mFileSize )
);
} 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
return Status::newGood(
array('result' => 1, 'done' => 1, 'resultUrl' => $file->getDescriptionUrl() )
);
}
return Status::newGood();
}
/**
* 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 ( $this->status->isOK() ) {
$this->repoPath = $this->status->value;
$_SESSION['wsUploadData'][$this->sessionKey]['repoPath'] = $this->repoPath;
}
return;
}
if ( $this->getRealPath( $this->repoPath ) ) {
$this->status = $this->appendToUploadFile( $this->repoPath, $this->mTempPath );
} else {
$this->status = Status::newFatal( 'filenotfound', $this->repoPath );
}
if ( $this->mFileSize > $wgMaxUploadSize )
$this->status = Status::newFatal( 'largefileserver' );
}
public function verifyUpload() {
if ( $this->chunkMode != self::DONE ) {
return array('status' => UploadBase::OK);
}
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 );
}
}