* 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
This commit is contained in:
Mark A. Hershberger 2010-02-10 10:36:11 +00:00
parent c386434108
commit c74fe71cd8
10 changed files with 263 additions and 142 deletions

View file

@ -84,6 +84,7 @@ $wgAutoloadLocalClasses = array(
'FakeTitle' => 'includes/FakeTitle.php',
'FakeMemCachedClient' => 'includes/ObjectCache.php',
'FauxRequest' => 'includes/WebRequest.php',
'FauxResponse' => 'includes/WebResponse.php',
'FeedItem' => 'includes/Feed.php',
'FeedUtils' => 'includes/FeedUtils.php',
'FileDeleteForm' => 'includes/FileDeleteForm.php',

View file

@ -712,6 +712,7 @@ class WebRequest {
class FauxRequest extends WebRequest {
private $wasPosted = false;
private $session = array();
private $response;
/**
* @param $data Array of *non*-urlencoded key => value pairs, the
@ -767,9 +768,8 @@ class FauxRequest extends WebRequest {
}
public function getSessionData( $key ) {
if( !isset( $this->session[$key] ) )
return null;
return $this->session[$key];
if( isset( $this->session[$key] ) )
return $this->session[$key];
}
public function setSessionData( $key, $data ) {
@ -780,4 +780,11 @@ class FauxRequest extends WebRequest {
return false;
}
public function response() {
/* Lazy initialization of response object for this request */
if ( !is_object( $this->response ) ) {
$this->response = new FauxResponse;
}
return $this->response;
}
}

View file

@ -6,7 +6,7 @@
*/
class WebResponse {
/**
/**
* Output a HTTP header, wrapper for PHP's
* header()
* @param $string String: header to output
@ -58,3 +58,31 @@ class WebResponse {
}
}
}
class FauxResponse extends WebResponse {
private $headers;
private $cookies;
public function header($string, $replace=true) {
list($key, $val) = explode(":", $string, 2);
if($replace || !isset($this->headers[$key])) {
$this->headers[$key] = $val;
}
}
public function getheader($key) {
return $this->headers[$key];
}
public function setcookie( $name, $value, $expire = 0 ) {
$this->cookies[$name] = $value;
}
public function getcookie( $name ) {
if ( isset($this->cookies[$name]) ) {
return $this->cookies[$name];
}
}
}

View file

@ -40,6 +40,10 @@ class ApiUpload extends ApiBase {
public function execute() {
global $wgUser, $wgAllowCopyUploads;
// Check whether upload is enabled
if ( !UploadBase::isEnabled() )
$this->dieUsageMsg( array( 'uploaddisabled' ) );
$this->getMain()->isWriteMode();
$this->mParams = $this->extractRequestParams();
$request = $this->getMain()->getRequest();
@ -53,15 +57,27 @@ class ApiUpload extends ApiBase {
// Add the uploaded file to the params array
$this->mParams['file'] = $request->getFileName( 'file' );
// Check whether upload is enabled
if ( !UploadBase::isEnabled() )
$this->dieUsageMsg( array( 'uploaddisabled' ) );
// One and only one of the following parameters is needed
$this->requireOnlyOneParameter( $this->mParams,
'sessionkey', 'file', 'url', 'enablechunks' );
if ( $this->mParams['sessionkey'] ) {
// Initialize $this->mUpload
if ( $this->mParams['enablechunks'] ) {
$this->mUpload = new UploadFromChunks();
$this->mUpload->initialize(
$request->getVal( 'done', null ),
$request->getVal( 'filename', null ),
$request->getVal( 'chunksession', null ),
$request->getFileTempName( 'chunk' ),
$request->getFileSize( 'chunk' ),
$request->getSessionData( 'wsUploadData' )
);
if ( !$this->mUpload->status->isOK() ) {
return $this->dieUsageMsg( $this->mUpload->status->getErrorsArray() );
}
} elseif ( $this->mParams['sessionkey'] ) {
/**
* Upload stashed in a previous request
*/
@ -72,30 +88,13 @@ class ApiUpload extends ApiBase {
$this->mUpload = new UploadFromStash();
$this->mUpload->initialize( $this->mParams['filename'],
$_SESSION['wsUploadData'][$this->mParams['sessionkey']] );
} else {
} elseif ( isset( $this->mParams['filename'] ) ) {
/**
* Upload from url, etc
* Parameter filename is required
*/
if ( !isset( $this->mParams['filename'] ) )
$this->dieUsageMsg( array( 'missingparam', 'filename' ) );
// Initialize $this->mUpload
if ( $this->mParams['enablechunks'] ) {
$this->mUpload = new UploadFromChunks();
$this->mUpload->initialize(
$request->getVal( 'done', null ),
$request->getVal( 'filename', null ),
$request->getVal( 'chunksessionkey', null ),
$request->getFileTempName( 'chunk' ),
$request->getFileSize( 'chunk' ),
$request->getSessionData( 'wsUploadData' )
);
if ( !$this->mUpload->status->isOK() ) {
return $this->dieUsageMsg( $this->mUpload->status->getErrorsArray() );
}
} elseif ( isset( $this->mParams['file'] ) ) {
if ( isset( $this->mParams['file'] ) ) {
$this->mUpload = new UploadFromFile();
$this->mUpload->initialize(
$this->mParams['filename'],
@ -120,7 +119,7 @@ class ApiUpload extends ApiBase {
return $this->dieUsage( $status->getWikiText(), 'fetchfileerror' );
}
}
}
} else $this->dieUsageMsg( array( 'missingparam', 'filename' ) );
if ( !isset( $this->mUpload ) )
$this->dieUsage( 'No upload module set', 'nomodule' );
@ -242,6 +241,8 @@ class ApiUpload extends ApiBase {
$this->getResult()->setIndexedTagName( $result['details'], 'error' );
$this->dieUsage( 'An internal error occurred', 'internal-error', 0, $error );
} elseif( isset($status->value->uploadUrl) ) {
return $status->value;
}
$file = $this->mUpload->getLocalFile();
@ -272,7 +273,7 @@ class ApiUpload extends ApiBase {
'ignorewarnings' => false,
'file' => null,
'enablechunks' => false,
'chunksessionkey' => null,
'chunksession' => null,
'chunk' => null,
'done' => false,
'url' => null,
@ -295,7 +296,7 @@ class ApiUpload extends ApiBase {
'ignorewarnings' => 'Ignore any warnings',
'file' => 'File contents',
'enablechunks' => 'Set to use chunk mode; see http://firefogg.org/dev/chunk_post.html for protocol',
'chunksessionkey' => 'The session key, established on the first contact during the chunked upload',
'chunksession' => 'The session key, established on the first contact during the chunked upload',
'chunk' => 'The data in this chunk of a chunked upload',
'done' => 'Set to 1 on the last chunk of a chunked upload',
'url' => 'Url to fetch the file from',

View file

@ -227,6 +227,33 @@ class FSRepo extends FileRepo {
return $status;
}
function append( $srcPath, $toAppendPath ) {
$status = $this->newGood();
// Resolve the virtual URL
if ( self::isVirtualUrl( $srcPath ) ) {
$srcPath = $this->resolveVirtualUrl( $srcPath );
}
// Make sure the files are there
if ( !is_file( $srcPath ) )
$status->fatal( 'append-src-filenotfound', $srcPath );
if ( !is_file( $toAppendPath ) )
$status->fatal( 'append-toappend-filenotfound', $toAppendPath );
// Do the append
if( file_put_contents( $srcPath, file_get_contents( $toAppendPath ), FILE_APPEND ) ) {
$status->value = $srcPath;
} else {
$status->fatal( 'fileappenderror', $toAppendPath, $srcPath);
}
// Remove the source file
unlink( $toAppendPath );
return $status;
}
/**
* Checks existence of specified array of files.
*
@ -575,7 +602,7 @@ class FSRepo extends FileRepo {
}
return strtr( $param, $this->simpleCleanPairs );
}
/**
* Chmod a file, supressing the warnings.
* @param String $path The path to change

View file

@ -30,7 +30,7 @@ abstract class FileRepo {
// Optional settings
$this->initialCapital = MWNamespace::isCapitalized( NS_FILE );
foreach ( array( 'descBaseUrl', 'scriptDirUrl', 'articleUrl', 'fetchDescription',
'thumbScriptUrl', 'initialCapital', 'pathDisclosureProtection',
'thumbScriptUrl', 'initialCapital', 'pathDisclosureProtection',
'descriptionCacheExpiry', 'hashLevels', 'url', 'thumbUrl' ) as $var )
{
if ( isset( $info[$var] ) ) {
@ -87,7 +87,7 @@ abstract class FileRepo {
*
* ignoreRedirect: If true, do not follow file redirects
*
* private: If true, return restricted (deleted) files if the current
* private: If true, return restricted (deleted) files if the current
* user is allowed to view them. Otherwise, such files will not
* be found.
*/
@ -123,12 +123,12 @@ abstract class FileRepo {
}
}
}
# Now try redirects
if ( !empty( $options['ignoreRedirect'] ) ) {
return false;
}
$redir = $this->checkRedirect( $title );
$redir = $this->checkRedirect( $title );
if( $redir && $redir->getNamespace() == NS_FILE) {
$img = $this->newFile( $redir );
if( !$img ) {
@ -141,9 +141,9 @@ abstract class FileRepo {
}
return false;
}
/*
* Find many files at once.
* Find many files at once.
* @param array $items, an array of titles, or an array of findFile() options with
* the "title" option giving the title. Example:
*
@ -168,7 +168,7 @@ abstract class FileRepo {
}
return $result;
}
/**
* Create a new File object from the local repository
* @param mixed $sha1 SHA-1 key
@ -189,14 +189,14 @@ abstract class FileRepo {
return call_user_func( $this->fileFactoryKey, $sha1, $this );
}
}
/**
* Find an instance of the file with this key, created at the specified time
* Returns false if the file does not exist. Repositories not supporting
* version control should return false if the time is specified.
*
* @param string $sha1 string
* @param array $options Option array, same as findFile().
* @param array $options Option array, same as findFile().
*/
function findFileFromKey( $sha1, $options = array() ) {
if ( !is_array( $options ) ) {
@ -234,7 +234,7 @@ abstract class FileRepo {
function getThumbScriptUrl() {
return $this->thumbScriptUrl;
}
/**
* Get the URL corresponding to one of the four basic zones
* @param String $zone One of: public, deleted, temp, thumb
@ -280,7 +280,7 @@ abstract class FileRepo {
return $path;
}
}
/**
* Get a relative path including trailing slash, e.g. f/fa/
* If the repo is not hashed, returns an empty string
@ -397,6 +397,8 @@ abstract class FileRepo {
*/
abstract function storeTemp( $originalName, $srcPath );
abstract function append( $srcPath, $toAppendPath );
/**
* Remove a temporary file or mark it for garbage collection
* @param string $virtualUrl The virtual URL returned by storeTemp
@ -587,14 +589,14 @@ abstract class FileRepo {
/**
* Invalidates image redirect cache related to that image
* Doesn't do anything for repositories that don't support image redirects.
*
*
* STUB
* @param Title $title Title of image
*/
*/
function invalidateImageRedirect( $title ) {}
/**
* Get an array or iterator of file objects for files that have a given
* Get an array or iterator of file objects for files that have a given
* SHA-1 content hash.
*
* STUB
@ -602,9 +604,9 @@ abstract class FileRepo {
function findBySha1( $hash ) {
return array();
}
/**
* Get the human-readable name of the repo.
* Get the human-readable name of the repo.
* @return string
*/
public function getDisplayName() {
@ -616,12 +618,12 @@ abstract class FileRepo {
if ( !wfEmptyMsg( 'shared-repo-name-' . $this->name, $repoName ) ) {
return $repoName;
}
return wfMsg( 'shared-repo' );
return wfMsg( 'shared-repo' );
}
/**
* Get a key on the primary cache for this repository.
* Returns false if the repository's cache is not accessible at this site.
* Returns false if the repository's cache is not accessible at this site.
* The parameters are the parts of the key, as for wfMemcKey().
*
* STUB
@ -631,7 +633,7 @@ abstract class FileRepo {
}
/**
* Get a key for this repo in the local cache domain. These cache keys are
* Get a key for this repo in the local cache domain. These cache keys are
* not shared with remote instances of the repo.
* The parameters are the parts of the key, as for wfMemcKey().
*/

View file

@ -22,7 +22,7 @@ class ForeignAPIRepo extends FileRepo {
var $apiThumbCacheExpiry = 86400;
protected $mQueryCache = array();
protected $mFileExists = array();
function __construct( $info ) {
parent::__construct( $info );
$this->mApiBase = $info['apibase']; // http://commons.wikimedia.org/w/api.php
@ -42,7 +42,7 @@ class ForeignAPIRepo extends FileRepo {
$this->thumbUrl = $this->url . '/thumb';
}
}
/**
* Per docs in FileRepo, this needs to return false if we don't support versioned
* files. Well, we don't.
@ -63,13 +63,16 @@ class ForeignAPIRepo extends FileRepo {
function storeTemp( $originalName, $srcPath ) {
return false;
}
function append( $srcPath, $toAppendPath ){
return false;
}
function publishBatch( $triplets, $flags = 0 ) {
return false;
}
function deleteBatch( $sourceDestPairs ) {
return false;
}
function fileExistsBatch( $files, $flags = 0 ) {
$results = array();
@ -99,10 +102,10 @@ class ForeignAPIRepo extends FileRepo {
function getFileProps( $virtualUrl ) {
return false;
}
protected function queryImage( $query ) {
$data = $this->fetchImageQuery( $query );
if( isset( $data['query']['pages'] ) ) {
foreach( $data['query']['pages'] as $pageid => $info ) {
if( isset( $info['imageinfo'][0] ) ) {
@ -112,10 +115,10 @@ class ForeignAPIRepo extends FileRepo {
}
return false;
}
protected function fetchImageQuery( $query ) {
global $wgMemc;
$url = $this->mApiBase .
'?' .
wfArrayToCgi(
@ -123,7 +126,7 @@ class ForeignAPIRepo extends FileRepo {
array(
'format' => 'json',
'action' => 'query' ) ) );
if( !isset( $this->mQueryCache[$url] ) ) {
$key = $this->getLocalCacheKey( 'ForeignAPIRepo', 'Metadata', md5( $url ) );
$data = $wgMemc->get( $key );
@ -143,14 +146,14 @@ class ForeignAPIRepo extends FileRepo {
}
return FormatJson::decode( $this->mQueryCache[$url], true );
}
function getImageInfo( $title, $time = false ) {
return $this->queryImage( array(
'titles' => 'Image:' . $title->getText(),
'iiprop' => 'timestamp|user|comment|url|size|sha1|metadata|mime',
'prop' => 'imageinfo' ) );
}
function findBySha1( $hash ) {
$results = $this->fetchImageQuery( array(
'aisha1base36' => $hash,
@ -164,7 +167,7 @@ class ForeignAPIRepo extends FileRepo {
}
return $ret;
}
function getThumbUrl( $name, $width=-1, $height=-1 ) {
$info = $this->queryImage( array(
'titles' => 'Image:' . $name,
@ -179,14 +182,14 @@ class ForeignAPIRepo extends FileRepo {
return false;
}
}
function getThumbUrlFromCache( $name, $width, $height ) {
global $wgMemc, $wgUploadPath, $wgServer, $wgUploadDirectory;
if ( !$this->canCacheThumbs() ) {
return $this->getThumbUrl( $name, $width, $height );
}
$key = $this->getLocalCacheKey( 'ForeignAPIRepo', 'ThumbUrl', $name );
if ( $thumbUrl = $wgMemc->get($key) ) {
wfDebug("Got thumb from local cache. $thumbUrl \n");
@ -194,7 +197,7 @@ class ForeignAPIRepo extends FileRepo {
}
else {
$foreignUrl = $this->getThumbUrl( $name, $width, $height );
// We need the same filename as the remote one :)
$fileName = rawurldecode( pathinfo( $foreignUrl, PATHINFO_BASENAME ) );
$path = 'thumb/' . $this->getHashPath( $name ) . $name . "/";
@ -213,7 +216,7 @@ class ForeignAPIRepo extends FileRepo {
return $localUrl;
}
}
/**
* @see FileRepo::getZoneUrl()
*/

View file

@ -14,6 +14,9 @@ class NullRepo extends FileRepo {
function storeTemp( $originalName, $srcPath ) {
return false;
}
function append( $srcPath, $toAppendPath ){
return false;
}
function publishBatch( $triplets, $flags = 0 ) {
return false;
}

View file

@ -22,7 +22,6 @@ class UploadFromChunks extends UploadBase {
protected $chunkMode; // INIT, CHUNK, DONE
protected $sessionKey;
protected $comment;
protected $fileSize = 0;
protected $repoPath;
protected $pageText;
protected $watch;
@ -37,9 +36,8 @@ class UploadFromChunks extends UploadBase {
throw new MWException( 'not implemented' );
}
public function initialize( $done, $filename, $sessionKey, $path,
$fileSize, $sessionData )
{
public function initialize( $done, $filename, $sessionKey, $path, $fileSize, $sessionData ) {
global $wgTmpDirectory;
$this->status = new Status;
$this->initFromSessionKey( $sessionKey, $sessionData );
@ -47,7 +45,7 @@ class UploadFromChunks extends UploadBase {
if ( !$this->sessionKey && !$done ) {
// session key not set, init the chunk upload system:
$this->chunkMode = self::INIT;
$this->mDesiredDestName = $filename;
$this->initializePathInfo( $filename, $path, 0, true);
} else if ( $this->sessionKey && !$done ) {
$this->chunkMode = self::CHUNK;
} else if ( $this->sessionKey && $done ) {
@ -55,7 +53,7 @@ class UploadFromChunks extends UploadBase {
}
if ( $this->chunkMode == self::CHUNK || $this->chunkMode == self::DONE ) {
$this->mTempPath = $path;
$this->fileSize += $fileSize;
$this->mFileSize += $fileSize;
}
}
@ -128,24 +126,25 @@ class UploadFromChunks extends UploadBase {
// 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() );
echo FormatJson::encode( array(
'uploadUrl' => wfExpandUrl( wfScript( 'api' ) ) . "?action=upload&" .
"token={$token}&format=json&enablechunks=true&chunksessionkey=" .
$this->setupChunkSession( $comment, $pageText, $watch ) ) );
$wgOut->disable();
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 ) {
$status = $this->appendChunk();
if ( !$status->isOK() ) {
return $status;
$this->appendChunk();
if ( !$this->status->isOK() ) {
return $this->status;
}
// return success:
// firefogg expects a specific result
// http://www.firefogg.org/dev/chunk_post.html
echo FormatJson::encode(
array( 'result' => 1, 'filesize' => $this->fileSize )
return Status::newGood(
array( 'result' => 1, 'filesize' => $this->mFileSize )
);
$wgOut->disable();
} else if ( $this->chunkMode == self::DONE ) {
if ( !$comment )
$comment = $this->comment;
@ -164,12 +163,9 @@ class UploadFromChunks extends UploadBase {
// firefogg expects a specific result
// http://www.firefogg.org/dev/chunk_post.html
echo FormatJson::encode( array(
'result' => 1,
'done' => 1,
'resultUrl' => $file->getDescriptionUrl() )
return Status::newGood(
array('result' => 1, 'done' => 1, 'resultUrl' => $file->getDescriptionUrl() )
);
$wgOut->disable();
}
return Status::newGood();
@ -199,18 +195,18 @@ class UploadFromChunks extends UploadBase {
if ( !$this->repoPath ) {
$this->status = $this->saveTempUploadedFile( $this->mDesiredDestName, $this->mTempPath );
if ( $status->isOK() ) {
$this->repoPath = $status->value;
if ( $this->status->isOK() ) {
$this->repoPath = $this->status->value;
$_SESSION['wsUploadData'][$this->sessionKey]['repoPath'] = $this->repoPath;
}
return $status;
return;
}
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 )
if ( $this->mFileSize > $wgMaxUploadSize )
$this->status = Status::newFatal( 'largefileserver' );
}

View file

@ -9,33 +9,10 @@ class UploadFromChunksTest extends ApiSetup {
$wgEnableUploads=true;
ini_set('file_loads', true);
}
function testGetTitle() {
$filename = tempnam( wfTempDir(), "" );
$c = new UploadFromChunks();
$c->initialize(false, "temp.txt", null, $filename, 0, null);
$this->assertEquals(null, $c->getTitle());
$c = new UploadFromChunks();
$c->initialize(false, "temp.png", null, $filename, 0, null);
$this->assertEquals(Title::makeTitleSafe(NS_FILE, "Temp.png"), $c->getTitle());
}
function testGetEditToken() {
}
function testInitFromSessionKey() {
parent::setup();
}
function testInitialize() {
}
function testSetupChunkSession() {
}
function makeChunk() {
$file = tempnam( wfTempDir(), "" );
$fh = fopen($file, "w");
@ -53,58 +30,134 @@ class UploadFromChunksTest extends ApiSetup {
}
function cleanChunk() {
unlink($_FILES['chunk']['tmp_name']);
if(file_exists($_FILES['chunk']['tmp_name']))
unlink($_FILES['chunk']['tmp_name']);
}
function doApiRequest($params) {
$session = isset( $_SESSION ) ? $_SESSION : array();
$req = new FauxRequest($params, true, $session);
$module = new ApiMain($req, true);
$module->execute();
return $module->getResultData();
}
function testGetTitle() {
$filename = tempnam( wfTempDir(), "" );
$c = new UploadFromChunks();
$c->initialize(false, "temp.txt", null, $filename, 0, null);
$this->assertEquals(null, $c->getTitle());
$c = new UploadFromChunks();
$c->initialize(false, "temp.png", null, $filename, 0, null);
$this->assertEquals(Title::makeTitleSafe(NS_FILE, "Temp.png"), $c->getTitle());
}
function testLogin() {
$data = $this->doApiRequest(array('action' => 'login',
"lgname" => self::$userName,
"lgpassword" => self::$passWord ) );
$this->assertArrayHasKey("login", $data);
$this->assertArrayHasKey("result", $data['login']);
$this->assertEquals("Success", $data['login']['result']);
return $data;
}
/**
* @depends testLogin
*/
function testGetEditToken($data) {
global $wgUser;
$wgUser = User::newFromName(self::$userName);
$wgUser->load();
$data = $this->doApiRequest(array('action' => 'query',
'prop' => 'info',
'intoken' => 'edit'));
}
function testSetupChunkSession() {
}
/**
* @expectedException UsageException
*/
function testPerformUploadInitError() {
global $wgUser;
$wgUser = User::newFromId(1);
$token = $wgUser->editToken();
$this->makeChunk();
$req = new FauxRequest(
array('action' => 'upload',
'enablechunks' => '1',
'filename' => 'test.png',
'token' => $token,
));
$module = new ApiMain($req, true);
$module->execute();
}
function testPerformUploadInitSuccess() {
/**
* @depends testLogin
*/
function testPerformUploadInitSuccess($login) {
global $wgUser;
$wgUser = User::newFromId(1);
$wgUser = User::newFromName(self::$userName);
$token = $wgUser->editToken();
$this->makeChunk();
$req = new FauxRequest(
$data = $this->doApiRequest(
array('action' => 'upload',
'enablechunks' => '1',
'filename' => 'test.png',
'token' => $token,
));
$module = new ApiMain($req, true);
$module->execute();
$this->assertArrayHasKey("upload", $data);
$this->assertArrayHasKey("uploadUrl", $data['upload']);
return array('data' => $data, 'session' => $_SESSION, 'token' => $token);
}
function testAppendToUploadFile() {
/**
* @depends testPerformUploadInitSuccess
*/
function testAppendChunk($combo) {
global $wgUser;
$data = $combo['data'];
$_SESSION = $combo['session'];
$wgUser = User::newFromName(self::$userName);
$token = $wgUser->editToken();
$url = $data['upload']['uploadUrl'];
$params = wfCgiToArray(substr($url, strpos($url, "?")));
for($i=0;$i<10;$i++) {
$this->makeChunk();
$data = $this->doApiRequest($params);
$this->cleanChunk();
}
return array('data' => $data, 'session' => $_SESSION, 'token' => $token, 'params' => $params);
}
function testAppendChunk() {
/**
* @depends testAppendChunk
*/
function testUploadChunkDone($combo) {
global $wgUser;
$data = $combo['data'];
$params = $combo['params'];
$_SESSION = $combo['session'];
$wgUser = User::newFromName(self::$userName);
$token = $wgUser->editToken();
$params['done'] = 1;
$this->makeChunk();
$data = $this->doApiRequest($params);
$this->cleanChunk();
}
function testPeformUploadChunk() {
}
function testPeformUploadDone() {
}
}