8 * first destination checks are made (if ignorewarnings is not checked) errors / warning is returned.
10 * we return the uploadUrl
11 * we then accept chunk uploads from the client.
12 * return chunk id on each POSTED chunk
13 * once the client posts done=1 concatenated the files together.
14 * more info at: http://firefogg.org/dev/chunk_post.html
16 class UploadFromChunks
extends UploadBase
{
18 var $chunk_mode; // init, chunk, done
19 var $mSessionKey = false;
20 var $status = array();
25 public function initializeFromRequest( &$request ){
26 //should merge initializeFromParams (but just needs to be working atm)
28 public function initializeFromParams( $param, &$request ) {
29 $this->initFromSessionKey( $param['chunksessionkey'], $request );
30 // set the chunk mode:
31 if( !$this->mSessionKey
&& !$param['done'] ){
32 // session key not set init the chunk upload system:
33 $this->chunk_mode
= UploadFromChunks
::INIT
;
34 $this->mDesiredDestName
= $param['filename'];
35 } else if( $this->mSessionKey
&& !$param['done'] ){
36 // this is a chunk piece
37 $this->chunk_mode
= UploadFromChunks
::CHUNK
;
38 } else if( $this->mSessionKey
&& $param['done'] ){
39 // this is the last chunk
40 $this->chunk_mode
= UploadFromChunks
::DONE
;
42 if( $this->chunk_mode
== UploadFromChunks
::CHUNK ||
43 $this->chunk_mode
== UploadFromChunks
::DONE
){
44 // set chunk related vars:
45 $this->mTempPath
= $request->getFileTempName( 'chunk' );
46 $this->mFileSize
= $request->getFileSize( 'chunk' );
51 static function isValidRequest( $request ) {
52 $sessionData = $request->getSessionData( 'wsUploadData' );
53 if( !self
::isValidSessionKey(
54 $request->getInt( 'wpSessionKey' ),
57 // check for the file:
58 return (bool)$request->getFileTempName( 'file' );
61 /* check warnings depending on chunk_mode */
62 function checkWarnings(){
67 function isEmptyFile(){
68 // does not apply to chunk init
69 if( $this->chunk_mode
== UploadFromChunks
::INIT
){
72 return parent
::isEmptyFile();
77 * Verify whether the upload is sane.
78 * Returns self::OK or else an array with error information
80 function verifyUpload() {
81 // no checks on chunk upload mode:
82 if( $this->chunk_mode
== UploadFromChunks
::INIT
)
83 return array( 'status' => self
::OK
);
85 // verify on init and last chunk request
86 if( $this->chunk_mode
== UploadFromChunks
::CHUNK ||
87 $this->chunk_mode
== UploadFromChunks
::DONE
)
88 return parent
::verifyUpload();
91 // only run verifyFile on completed uploaded chunks
92 function verifyFile(){
93 if( $this->chunk_mode
== UploadFromChunks
::DONE
){
94 // first append last chunk (so we can do a real verifyFile check... (check file type etc)
95 $status = $this->doChunkAppend();
96 if( $status->isOK() ){
97 $this->mTempPath
= $this->getRealPath( $this->mTempAppendPath
);
98 // verify the completed merged chunks as if it was the file that got uploaded:
99 return parent
::verifyFile( $this->mTempPath
);
101 // conflict of status returns (have to return the error ary) ... why we don't consistantly use a status object is beyond me..
102 return $status->getErrorsArray();
110 * @param string $srcPath the source path
111 * @returns the real path if it was a virtual url
113 function getRealPath( $srcPath ){
114 $repo = RepoGroup
::singleton()->getLocalRepo();
115 if ( $repo->isVirtualUrl( $srcPath ) ) {
116 return $repo->resolveVirtualUrl( $srcPath );
121 // pretty ugly inter-mixing of mParam and local vars
122 function setupChunkSession( $summary, $comment, $watch ) {
123 $this->mSessionKey
= $this->getSessionKey();
124 $_SESSION['wsUploadData'][$this->mSessionKey
] = array(
125 'mComment' => $comment,
126 'mSummary' => $summary,
128 'mIgnorewarnings' => true, //ignore warning on chunk uploads (for now)
129 'mFilteredName' => $this->mFilteredName
,
130 'mTempAppendPath' => null, // the repo append path (not temporary local node mTempPath)
131 'mDesiredDestName' => $this->mDesiredDestName
,
132 'version' => self
::SESSION_VERSION
,
134 return $this->mSessionKey
;
137 function initFromSessionKey( $sessionKey, $request ){
138 if( !$sessionKey ||
empty( $sessionKey ) ){
141 $this->mSessionKey
= $sessionKey;
142 // load the sessionData array:
143 $sessionData = $request->getSessionData( 'wsUploadData' );
145 if( isset( $sessionData[$this->mSessionKey
]['version'] ) &&
146 $sessionData[$this->mSessionKey
]['version'] == self
::SESSION_VERSION
) {
147 // update the local object from the session
148 $this->mComment
= $sessionData[$this->mSessionKey
]['mComment'];
149 $this->mSummary
= $sessionData[$this->mSessionKey
]['mSummary'];
150 $this->mWatch
= $sessionData[$this->mSessionKey
]['mWatch'];
151 $this->mIgnorewarnings
= $sessionData[$this->mSessionKey
]['mIgnorewarnings'];
152 $this->mFilteredName
= $sessionData[$this->mSessionKey
]['mFilteredName'];
153 $this->mTempAppendPath
= $sessionData[$this->mSessionKey
]['mTempAppendPath'];
154 $this->mDesiredDestName
= $sessionData[$this->mSessionKey
]['mDesiredDestName'];
156 $this->status
= array( 'error' => 'missing session data' );
161 // Lets us return an api result (as flow for chunk uploads is kind of different than others.
162 function performUpload( $summary = '', $comment = '', $watch = '', $user ){
165 if( $this->chunk_mode
== UploadFromChunks
::INIT
){
166 // firefogg expects a specific result per:
167 // http://www.firefogg.org/dev/chunk_post.html
169 // it's okay to return the token here because
170 // a) the user must have requested the token to get here and
171 // b) should only happen over POST
172 // c) (we need the token to validate chunks are coming from a non-xss request)
173 $token = urlencode( $wgUser->editToken() );
175 echo FormatJson
::encode( array(
176 'uploadUrl' => wfExpandUrl( wfScript( 'api' ) ) . "?action=upload&".
177 "token={$token}&format=json&enablechunks=true&chunksessionkey=".
178 $this->setupChunkSession( $summary, $comment, $watch ) ) );
180 } else if( $this->chunk_mode
== UploadFromChunks
::CHUNK
){
181 $status = $this->doChunkAppend();
182 if( $status->isOK() ){
184 // firefogg expects a specific result per:
185 // http://www.firefogg.org/dev/chunk_post.html
187 echo FormatJson
::encode( array(
189 'filesize' => filesize( $this->getRealPath( $this->mTempAppendPath
) )
199 } else if( $this->chunk_mode
== UploadFromChunks
::DONE
){
200 // update the values from the local (session init) if not paseed again)
202 $summary = $this->mSummary
;
205 $comment = $this->mComment
;
208 $watch = $this->mWatch
;
209 $status = parent
::performUpload( $summary, $comment, $watch, $user );
210 if( !$status->isGood() ) {
213 $file = $this->getLocalFile();
214 // firefogg expects a specific result per:
215 // http://www.firefogg.org/dev/chunk_post.html
217 echo FormatJson
::encode( array(
220 'resultUrl' => $file->getDescriptionUrl()
228 // append the given chunk to the temporary uploaded file. (if no temporary uploaded file exists created it.
229 function doChunkAppend(){
230 global $wgMaxUploadSize;
231 // if we don't have a mTempAppendPath to generate a file from the chunk packaged var:
232 if( !$this->mTempAppendPath
){
234 // make a chunk store path. (append tmp file to chunk)
235 $status = $this->saveTempUploadedFile( $this->mDestName
, $this->mTempPath
);
237 if( $status->isOK() ) {
238 $this->mTempAppendPath
= $status->value
;
239 $_SESSION['wsUploadData'][$this->mSessionKey
]['mTempAppendPath'] = $this->mTempAppendPath
;
243 if( is_file( $this->getRealPath( $this->mTempAppendPath
) ) ){
244 $status = $this->appendToUploadFile( $this->mTempAppendPath
, $this->mTempPath
);
246 $status = Status
::newFatal( 'filenotfound', $this->mTempAppendPath
);
248 //check to make sure we have not expanded beyond $wgMaxUploadSize
249 if( filesize( $this->getRealPath( $this->mTempAppendPath
) ) > $wgMaxUploadSize )
250 $status = Status
::newFatal( 'largefileserver' );