6 * First, destination checks are made, and, if ignorewarnings is not
7 * checked, errors / warning is returned.
9 * 1. We return the uploadUrl.
10 * 2. We then accept chunk uploads from the client.
11 * 3. Return chunk id on each POSTED chunk.
12 * 4. Once the client posts "done=1", the files are concatenated together.
14 * (More info at: http://firefogg.org/dev/chunk_post.html)
16 class UploadFromChunks
extends UploadBase
{
22 // STYLE NOTE: Coding guidelines says the 'm' prefix for object
23 // member variables is discouraged in new code but "stay
24 // consistent within a class". UploadFromChunks is new, but extends
25 // UploadBase which has the 'm' prefix. I'm eschewing the prefix for
26 // member variables of this class.
27 protected $chunkMode; // INIT, CHUNK, DONE
28 protected $sessionKey;
30 protected $fileSize = 0;
37 // Parent class requires this function even though it is only
38 // used from SpecialUpload.php and we don't do chunked uploading
39 // from SpecialUpload -- best to raise an exception for
41 public function initializeFromRequest( &$request ) {
42 throw new MWException( 'not implemented' );
45 public function initialize( $done, $filename, $sessionKey, $path,
46 $fileSize, $sessionData ) {
47 $this->initFromSessionKey( $sessionKey, $sessionData );
49 if ( !$this->sessionKey
&& !$done ) {
50 // session key not set, init the chunk upload system:
51 $this->chunkMode
= self
::INIT
;
52 $this->mDesiredDestName
= $filename;
53 } else if ( $this->sessionKey
&& !$done ) {
54 $this->chunkMode
= self
::CHUNK
;
55 } else if ( $this->sessionKey
&& $done ) {
56 $this->chunkMode
= self
::DONE
;
59 if ( $this->chunkMode
== self
::CHUNK ||
$this->chunkMode
== self
::DONE
) {
60 $this->mTempPath
= $path;
61 $this->fileSize +
= $fileSize;
66 * Set session information for chunked uploads and allocate a unique key.
67 * @param $comment string
68 * @param $pageText string
69 * @param $watch boolean
71 * @returns string the session key for this chunked upload
73 protected function setupChunkSession( $comment, $pageText, $watch ) {
74 $this->sessionKey
= $this->getSessionKey();
75 $_SESSION['wsUploadData'][$this->sessionKey
] = array(
76 'comment' => $comment,
77 'pageText' => $pageText,
79 'mFilteredName' => $this->mFilteredName
,
81 'mDesiredDestName' => $this->mDesiredDestName
,
82 'version' => self
::SESSION_VERSION
,
84 return $this->sessionKey
;
88 * Initialize a continuation of a chunked upload from a session key
89 * @param $sessionKey string
90 * @param $request WebRequest
94 protected function initFromSessionKey( $sessionKey, $sessionData ) {
95 if ( !$sessionKey ||
empty( $sessionKey ) ) {
96 $this->status
= Status
::newFromFatal( 'Missing session data.' );
99 $this->sessionKey
= $sessionKey;
101 if ( isset( $sessionData[$this->sessionKey
]['version'] )
102 && $sessionData[$this->sessionKey
]['version'] == self
::SESSION_VERSION
) {
103 $this->comment
= $sessionData[$this->sessionKey
]['comment'];
104 $this->pageText
= $sessionData[$this->sessionKey
]['pageText'];
105 $this->watch
= $sessionData[$this->sessionKey
]['watch'];
106 $this->mFilteredName
= $sessionData[$this->sessionKey
]['mFilteredName'];
107 $this->repoPath
= $sessionData[$this->sessionKey
]['repoPath'];
108 $this->mDesiredDestName
= $sessionData[$this->sessionKey
]['mDesiredDestName'];
110 $this->status
= Status
::newFromFatal( 'Missing session data.' );
115 * Handle a chunk of the upload. Overrides the parent method
116 * because Chunked Uploading clients (i.e. Firefogg) require
117 * specific API responses.
118 * @see UploadBase::performUpload
120 public function performUpload( $comment, $pageText, $watch, $user ) {
121 wfDebug( "\n\n\performUpload(chunked): sum:" . $comment . ' c: ' . $pageText . ' w:' . $watch );
122 global $wgUser, $wgOut;
124 if ( $this->chunkMode
== self
::INIT
) {
125 // firefogg expects a specific result per:
126 // http://www.firefogg.org/dev/chunk_post.html
128 // it's okay to return the token here because
129 // a) the user must have requested the token to get here and
130 // b) should only happen over POST
131 // c) we need the token to validate chunks are coming from a non-xss request
132 $token = urlencode( $wgUser->editToken() );
134 echo FormatJson
::encode( array(
135 'uploadUrl' => wfExpandUrl( wfScript( 'api' ) ) . "?action=upload&" .
136 "token={$token}&format=json&enablechunks=true&chunksessionkey=" .
137 $this->setupChunkSession( $comment, $pageText, $watch ) ) );
139 } else if ( $this->chunkMode
== self
::CHUNK
) {
140 $status = $this->appendChunk();
141 if ( !$status->isOK() ) {
145 // firefogg expects a specific result
146 // http://www.firefogg.org/dev/chunk_post.html
148 echo FormatJson
::encode(
149 array( 'result' => 1, 'filesize' => $this->fileSize
)
152 } else if ( $this->chunkMode
== self
::DONE
) {
153 if ( $comment == '' )
154 $comment = $this->comment
;
156 if ( $pageText == '' )
157 $pageText = $this->pageText
;
160 $watch = $this->watch
;
162 $status = parent
::performUpload( $comment, $pageText, $watch, $user );
163 if ( !$status->isGood() ) {
166 $file = $this->getLocalFile();
168 // firefogg expects a specific result
169 // http://www.firefogg.org/dev/chunk_post.html
171 echo FormatJson
::encode( array(
174 'resultUrl' => $file->getDescriptionUrl() )
181 * Append a chunk to the Repo file
183 * @param string $srcPath Path to file to append from
184 * @param string $toAppendPath Path to file to append to
185 * @return Status Status
187 protected function appendToUploadFile( $srcPath, $toAppendPath ) {
188 $repo = RepoGroup
::singleton()->getLocalRepo();
189 $status = $repo->append( $srcPath, $toAppendPath );
194 * Append a chunk to the temporary file.
198 protected function appendChunk() {
199 global $wgMaxUploadSize;
201 if ( !$this->repoPath
) {
202 $this->status
= $this->saveTempUploadedFile( $this->mDesiredDestName
, $this->mTempPath
);
204 if ( $status->isOK() ) {
205 $this->repoPath
= $status->value
;
206 $_SESSION['wsUploadData'][$this->sessionKey
]['repoPath'] = $this->repoPath
;
210 if ( $this->getRealPath( $this->repoPath
) ) {
211 $this->status
= $this->appendToUploadFile( $this->repoPath
, $this->mTempPath
);
213 $this->status
= Status
::newFatal( 'filenotfound', $this->repoPath
);
216 if ( $this->fileSize
> $wgMaxUploadSize )
217 $this->status
= Status
::newFatal( 'largefileserver' );