$this->mParams['file'] = $request->getFileName( 'file' );
// Select an upload module
- $this->selectUploadModule();
+ if ( !$this->selectUploadModule() ) {
+ // This is not a true upload, but a status request or similar
+ return;
+ }
if ( !isset( $this->mUpload ) ) {
$this->dieUsage( 'No upload module set', 'nomodule' );
}
}
/**
- * Select an upload module and set it to mUpload. Dies on failure.
+ * Select an upload module and set it to mUpload. Dies on failure. If the
+ * request was a status request and not a true upload, returns false;
+ * otherwise true
+ *
+ * @return bool
*/
protected function selectUploadModule() {
$request = $this->getMain()->getRequest();
// One and only one of the following parameters is needed
$this->requireOnlyOneParameter( $this->mParams,
- 'sessionkey', 'file', 'url' );
+ 'sessionkey', 'file', 'url', 'statuskey' );
+ if ( $this->mParams['statuskey'] ) {
+ // Status request for an async upload
+ $sessionData = UploadFromUrlJob::getSessionData( $this->mParams['statuskey'] );
+ if ( !isset( $sessionData['result'] ) ) {
+ $this->dieUsage();
+ }
+ if ( $sessionData['result'] == 'Warning' ) {
+ $sessionData['warnings'] = $this->transformWarnings( $sessionData['warnings'] );
+ $sessionData['sessionkey'] = $this->mParams['statuskey'];
+ }
+ $this->getResult()->addValue( null, $this->getModuleName(), $sessionData );
+ return false;
+
+ }
+
+ // The following modules all require the filename parameter to be set
+ if ( is_null( $this->mParams['filename'] ) ) {
+ $this->dieUsageMsg( array( 'missingparam', 'filename' ) );
+ }
+
if ( $this->mParams['sessionkey'] ) {
// Upload stashed in a previous request
$sessionData = $request->getSessionData( UploadBase::getSessionKeyName() );
$async = false;
if ( $this->mParams['asyncdownload'] ) {
+ if ( $this->mParams['leavemessage'] && !$this->mParams['ignorewarnings'] ) {
+ $this->dieUsage( 'Using leavemessage without ignorewarnings is not supported',
+ 'missing-ignorewarnings' );
+ }
+
if ( $this->mParams['leavemessage'] ) {
$async = 'async-leavemessage';
} else {
$this->mParams['url'], $async );
}
+
+ return true;
}
/**
if ( !$this->mParams['ignorewarnings'] ) {
$warnings = $this->mUpload->checkWarnings();
if ( $warnings ) {
- // Add indices
- $this->getResult()->setIndexedTagName( $warnings, 'warning' );
-
- if ( isset( $warnings['duplicate'] ) ) {
- $dupes = array();
- foreach ( $warnings['duplicate'] as $key => $dupe )
- $dupes[] = $dupe->getName();
- $this->getResult()->setIndexedTagName( $dupes, 'duplicate' );
- $warnings['duplicate'] = $dupes;
- }
-
- if ( isset( $warnings['exists'] ) ) {
- $warning = $warnings['exists'];
- unset( $warnings['exists'] );
- $warnings[$warning['warning']] = $warning['file']->getName();
- }
-
$result['result'] = 'Warning';
- $result['warnings'] = $warnings;
+ $result['warnings'] = $this->transformWarnings( $warnings );
$sessionKey = $this->mUpload->stashSession();
if ( !$sessionKey ) {
}
return;
}
+
+ /**
+ * Transforms a warnings array returned by mUpload->checkWarnings() into
+ * something that can be directly used as API result
+ */
+ protected function transformWarnings( $warnings ) {
+ // Add indices
+ $this->getResult()->setIndexedTagName( $warnings, 'warning' );
+
+ if ( isset( $warnings['duplicate'] ) ) {
+ $dupes = array();
+ foreach ( $warnings['duplicate'] as $key => $dupe ) {
+ $dupes[] = $dupe->getName();
+ }
+ $this->getResult()->setIndexedTagName( $dupes, 'duplicate' );
+ $warnings['duplicate'] = $dupes;
+ }
+
+ if ( isset( $warnings['exists'] ) ) {
+ $warning = $warnings['exists'];
+ unset( $warnings['exists'] );
+ $warnings[$warning['warning']] = $warning['file']->getName();
+ }
+
+ return $warnings;
+ }
/**
* Perform the actual upload. Returns a suitable result array on success;
// requested so
return array(
'result' => 'Queued',
- 'sessionkey' => $error[0][1],
+ 'statuskey' => $error[0][1],
);
} else {
$this->getResult()->setIndexedTagName( $error, 'error' );
$params = array(
'filename' => array(
ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
),
'comment' => array(
ApiBase::PARAM_DFLT => ''
$params += array(
'asyncdownload' => false,
'leavemessage' => false,
+ 'statuskey' => null,
);
}
return $params;
$params += array(
'asyncdownload' => 'Make fetching a URL asynchronous',
'leavemessage' => 'If asyncdownload is used, leave a message on the user talk page if finished',
+ 'statuskey' => 'Fetch the upload status for this session key',
);
}
* @ingroup JobQueue
*/
class UploadFromUrlJob extends Job {
+ const SESSION_KEYNAME = 'wsUploadFromUrlJobData';
+
public $upload;
protected $user;
}
public function run() {
- # Until we find a way to store data in sessions, set leaveMessage to
- # true unconditionally
- $this->params['leaveMessage'] = true;
- # Similar for ignorewarnings. This is not really a good fallback, but
- # there is no easy way to get a wikitext formatted warning message to
- # show to the user
- $this->params['ignoreWarnings'] = true;
-
# Initialize this object and the upload object
$this->upload = new UploadFromUrl();
$this->upload->initialize(
if ( !$this->params['ignoreWarnings'] ) {
$warnings = $this->upload->checkWarnings();
if ( $warnings ) {
+ wfSetupSession( $this->params['sessionId'] );
+
if ( $this->params['leaveMessage'] ) {
$this->user->leaveUserMessage(
wfMsg( 'upload-warning-subj' ),
'warnings', $warnings );
}
- // FIXME: stash in session
+ # Stash the upload in the session
+ $this->upload->stashSession( $this->params['sessionKey'] );
+ session_write_close();
+
return true;
}
}
) );
}
} else {
+ wfSetupSession( $this->params['sessionId'] );
if ( $status->isOk() ) {
$this->storeResultInSession( 'Success',
- 'filename', $this->getLocalFile()->getName() );
+ 'filename', $this->upload->getLocalFile()->getName() );
} else {
$this->storeResultInSession( 'Failure',
'errors', $status->getErrorsArray() );
}
-
+ session_write_close();
}
}
/**
- * Store a result in the session data
- * THIS IS BROKEN. $_SESSION does not exist when using runJobs.php
+ * Store a result in the session data. Note that the caller is responsible
+ * for appropriate session_start and session_write_close calls.
*
* @param $result String: the result (Success|Warning|Failure)
* @param $dataKey String: the key of the extra data
* @param $dataValue Mixed: the extra data itself
*/
protected function storeResultInSession( $result, $dataKey, $dataValue ) {
- $session &= $_SESSION[UploadBase::getSessionKeyname()][$this->params['sessionKey']];
+ $session =& self::getSessionData( $this->params['sessionKey'] );
$session['result'] = $result;
$session[$dataKey] = $dataValue;
}
+
+ /**
+ * Initialize the session data. Sets the intial result to queued.
+ */
+ public function initializeSessionData() {
+ $session =& self::getSessionData( $this->params['sessionKey'] );
+ $$session['result'] = 'Queued';
+ }
+
+ public static function &getSessionData( $key ) {
+ if ( !isset( $_SESSION[self::SESSION_KEYNAME][$key] ) ) {
+ $_SESSION[self::SESSION_KEYNAME][$key] = array();
+ }
+ return $_SESSION[self::SESSION_KEYNAME][$key];
+ }
}
<?php
-/* Force User.php include for EDIT_TOKEN_SUFFIX */
-require_once dirname(__FILE__) . "/../../includes/User.php";
-
-class nullClass {
- public function handleOutput() { }
- public function purgeRedundantText() { }
-}
class UploadFromUrlTest extends ApiTestSetup {
- function setUp() {
+ public function setUp() {
global $wgEnableUploads, $wgAllowCopyUploads;
+ parent::setup();
$wgEnableUploads = true;
$wgAllowCopyUploads = true;
- parent::setup();
+ wfSetupSession();
ini_set( 'log_errors', 1 );
ini_set( 'error_reporting', 1 );
ini_set( 'display_errors', 1 );
+
+ if ( wfLocalFile( 'UploadFromUrlTest.png' )->exists() ) {
+ $this->deleteFile( 'UploadFromUrlTest.png' );
+ }
}
- function doApiRequest( $params, $data = null ) {
- $session = isset( $data[2] ) ? $data[2] : array();
- $_SESSION = $session;
-
- $req = new FauxRequest( $params, true, $session );
+ protected function doApiRequest( $params ) {
+ $sessionId = session_id();
+ session_write_close();
+
+ $req = new FauxRequest( $params, true, $_SESSION );
$module = new ApiMain( $req, true );
$module->execute();
- return array( $module->getResultData(), $req, $_SESSION );
+ wfSetupSession( $sessionId );
+ return array( $module->getResultData(), $req );
}
- function testClearQueue() {
+ /**
+ * Ensure that the job queue is empty before continuing
+ */
+ public function testClearQueue() {
while ( $job = Job::pop() ) { }
$this->assertFalse( $job );
}
- function testLogin() {
+ /**
+ * @todo Document why we test login, since the $wgUser hack used doesn't
+ * require login
+ */
+ public function testLogin() {
$data = $this->doApiRequest( array(
'action' => 'login',
'lgname' => self::$userName,
/**
* @depends testLogin
+ * @depends testClearQueue
*/
- function testSetupUrlDownload( $data ) {
- global $wgUser;
- $wgUser = User::newFromName( self::$userName );
- $wgUser->load();
- $data[2]['wsEditToken'] = $data[2]['wsToken'];
- $token = md5( $data[2]['wsToken'] ) . EDIT_TOKEN_SUFFIX;
+ public function testSetupUrlDownload( $data ) {
+ $token = self::$user->editToken();
$exception = false;
try {
$this->doApiRequest( array(
'action' => 'upload',
- ), $data );
+ ) );
} catch ( UsageException $e ) {
$exception = true;
$this->assertEquals( "The token parameter must be set", $e->getMessage() );
), $data );
} catch ( UsageException $e ) {
$exception = true;
- $this->assertEquals( "One of the parameters sessionkey, file, url is required",
+ $this->assertEquals( "One of the parameters sessionkey, file, url, statuskey is required",
$e->getMessage() );
}
$this->assertTrue( $exception, "Got exception" );
}
$this->assertTrue( $exception, "Got exception" );
- $wgUser->removeGroup( 'sysop' );
+ self::$user->removeGroup( 'sysop' );
$exception = false;
try {
$this->doApiRequest( array(
}
$this->assertTrue( $exception, "Got exception" );
- $wgUser->addGroup( '*' );
- $wgUser->addGroup( 'sysop' );
+ self::$user->addGroup( '*' );
+ self::$user->addGroup( 'sysop' );
$exception = false;
$data = $this->doApiRequest( array(
'action' => 'upload',
/**
* @depends testLogin
+ * @depends testClearQueue
*/
- function testDoDownload( $data ) {
- global $wgUser;
- $data[2]['wsEditToken'] = $data[2]['wsToken'];
- $token = md5( $data[2]['wsToken'] ) . EDIT_TOKEN_SUFFIX;
+ public function testAsyncUpload( $data ) {
+ $token = self::$user->editToken();
- $wgUser->addGroup( 'users' );
- $data = $this->doApiRequest( array(
- 'action' => 'upload',
- 'filename' => 'UploadFromUrlTest.png',
- 'url' => 'http://bits.wikimedia.org/skins-1.5/common/images/poweredby_mediawiki_88x31.png',
- 'asyncdownload' => 1,
- 'token' => $token,
- ), $data );
+ self::$user->addGroup( 'users' );
- $job = Job::pop();
- $this->assertEquals( 'UploadFromUrlJob', get_class( $job ) );
+ $data = $this->doAsyncUpload( $token, true );
+ $this->assertEquals( $data[0]['upload']['result'], 'Success' );
+ $this->assertEquals( $data[0]['upload']['filename'], 'UploadFromUrlTest.png' );
+ $this->assertTrue( wfLocalFile( $data[0]['upload']['filename'] )->exists() );
+
+ $this->deleteFile( 'UploadFromUrlTest.png' );
- $status = $job->run();
+ return $data;
+ }
+
+ /**
+ * @depends testLogin
+ * @depends testClearQueue
+ */
+ public function testAsyncUploadWarning( $data ) {
+ $token = self::$user->editToken();
+
+ self::$user->addGroup( 'users' );
- $this->assertTrue( $status );
+
+ $data = $this->doAsyncUpload( $token );
+
+ $this->assertEquals( $data[0]['upload']['result'], 'Warning' );
+ $this->assertTrue( isset( $data[0]['upload']['sessionkey'] ) );
+
+ $data = $this->doApiRequest( array(
+ 'action' => 'upload',
+ 'sessionkey' => $data[0]['upload']['sessionkey'],
+ 'filename' => 'UploadFromUrlTest.png',
+ 'ignorewarnings' => 1,
+ 'token' => $token,
+ ) );
+ $this->assertEquals( $data[0]['upload']['result'], 'Success' );
+ $this->assertEquals( $data[0]['upload']['filename'], 'UploadFromUrlTest.png' );
+ $this->assertTrue( wfLocalFile( $data[0]['upload']['filename'] )->exists() );
+
+ $this->deleteFile( 'UploadFromUrlTest.png' );
return $data;
}
/**
* @depends testLogin
+ * @depends testClearQueue
*/
- function testSyncDownload( $data ) {
- global $wgUser;
- $data[2]['wsEditToken'] = $data[2]['wsToken'];
- $token = md5( $data[2]['wsToken'] ) . EDIT_TOKEN_SUFFIX;
+ public function testSyncDownload( $data ) {
+ $token = self::$user->editToken();
$job = Job::pop();
$this->assertFalse( $job, 'Starting with an empty jobqueue' );
- //$this->deleteFile( 'UploadFromUrlTest.png' );
-
- $wgUser->addGroup( 'users' );
+ self::$user->addGroup( 'users' );
$data = $this->doApiRequest( array(
'action' => 'upload',
'filename' => 'UploadFromUrlTest.png',
$this->assertFalse( $job );
$this->assertEquals( 'Success', $data[0]['upload']['result'] );
+ $this->deleteFile( 'UploadFromUrlTest.png' );
return $data;
}
+
+ public function testLeaveMessage() {
+ $token = self::$user->editToken();
+
+ $talk = self::$user->getTalkPage();
+ if ( $talk->exists() ) {
+ $a = new Article( $talk );
+ $a->doDeleteArticle( '' );
+ }
+
+ $this->assertFalse( (bool)$talk->getArticleId( GAID_FOR_UPDATE ), 'User talk does not exist' );
+
+ $data = $this->doApiRequest( array(
+ 'action' => 'upload',
+ 'filename' => 'UploadFromUrlTest.png',
+ 'url' => 'http://bits.wikimedia.org/skins-1.5/common/images/poweredby_mediawiki_88x31.png',
+ 'asyncdownload' => 1,
+ 'token' => $token,
+ 'leavemessage' => 1,
+ 'ignorewarnings' => 1,
+ ) );
+
+ $job = Job::pop();
+ $job->run();
+
+ $this->assertTrue( wfLocalFile( 'UploadFromUrlTest.png' )->exists() );
+ $this->assertTrue( (bool)$talk->getArticleId( GAID_FOR_UPDATE ), 'User talk exists' );
+
+ $this->deleteFile( 'UploadFromUrlTest.png' );
+
+ $talkRev = Revision::newFromTitle( $talk );
+ $talkSize = $talkRev->getSize();
+
+ $exception = false;
+ try {
+ $data = $this->doApiRequest( array(
+ 'action' => 'upload',
+ 'filename' => 'UploadFromUrlTest.png',
+ 'url' => 'http://bits.wikimedia.org/skins-1.5/common/images/poweredby_mediawiki_88x31.png',
+ 'asyncdownload' => 1,
+ 'token' => $token,
+ 'leavemessage' => 1,
+ ) );
+ } catch ( UsageException $e ) {
+ $exception = true;
+ $this->assertEquals( 'Using leavemessage without ignorewarnings is not supported', $e->getMessage() );
+ }
+ $this->assertTrue( $exception );
+
+ $job = Job::pop();
+ $this->assertFalse( $job );
+
+ return;
+
+ // Broken until using leavemessage with ignorewarnings is supported
+ $job->run();
+
+ $this->assertFalse( wfLocalFile( 'UploadFromUrlTest.png' )->exists() );
+
+ $talkRev = Revision::newFromTitle( $talk );
+ $this->assertTrue( $talkRev->getSize() > $talkSize, 'New message left' );
+
+ }
+
/**
- * @depends testDoDownload
+ * Helper function to perform an async upload, execute the job and fetch
+ * the status
+ *
+ * @return array The result of action=upload&statuskey=key
*/
- function testVerifyDownload( $data ) {
- $t = Title::newFromText( "UploadFromUrlTest.png", NS_FILE );
-
- $this->assertTrue( $t->exists() );
-
- $this->deleteFile( 'UploadFromUrlTest.png' );
- }
+ private function doAsyncUpload( $token, $ignoreWarnings = false, $leaveMessage = false ) {
+ $params = array(
+ 'action' => 'upload',
+ 'filename' => 'UploadFromUrlTest.png',
+ 'url' => 'http://bits.wikimedia.org/skins-1.5/common/images/poweredby_mediawiki_88x31.png',
+ 'asyncdownload' => 1,
+ 'token' => $token,
+ );
+ if ( $ignoreWarnings ) {
+ $params['ignorewarnings'] = 1;
+ }
+ if ( $leaveMessage ) {
+ $params['leavemessage'] = 1;
+ }
+
+ $data = $this->doApiRequest( $params );
+ $this->assertEquals( $data[0]['upload']['result'], 'Queued' );
+ $this->assertTrue( isset( $data[0]['upload']['statuskey'] ) );
+ $statusKey = $data[0]['upload']['statuskey'];
+
+ $job = Job::pop();
+ $this->assertEquals( 'UploadFromUrlJob', get_class( $job ) );
+
+ $status = $job->run();
+ $this->assertTrue( $status );
+
+ $data = $this->doApiRequest( array(
+ 'action' => 'upload',
+ 'statuskey' => $statusKey,
+ 'token' => $token,
+ ) );
+
+ return $data;
+ }
+
/**
*
*/
- function deleteFile( $name ) {
+ protected function deleteFile( $name ) {
$t = Title::newFromText( $name, NS_FILE );
$this->assertTrue($t->exists(), "File '$name' exists");