here it is ... the upload-api, script-server, js2 (javascript phase2) branch merge...
[lhc/web/wiklou.git] / includes / upload / UploadFromChunks.php
1 <?php
2 /*
3 * first destination checks are made (if ignorewarnings is not checked) errors / warning is returned.
4 *
5 * we return the uploadUrl
6 * we then accept chunk uploads from the client.
7 * return chunk id on each POSTED chunk
8 * once the client posts done=1 concatenated the files together.
9 * more info at: http://firefogg.org/dev/chunk_post.html
10 */
11 class UploadFromChunks extends UploadBase {
12
13 var $chunk_mode; //init, chunk, done
14 var $mSessionKey = false;
15 var $status = array();
16
17 const INIT = 1;
18 const CHUNK = 2;
19 const DONE = 3;
20
21 function initializeFromParams( $param , &$request) {
22 $this->initFromSessionKey( $param['chunksessionkey'], $request );
23 //set the chunk mode:
24 if( !$this->mSessionKey && !$param['done'] ){
25 //session key not set init the chunk upload system:
26 $this->chunk_mode = UploadFromChunks::INIT;
27 $this->mDesiredDestName = $param['filename'];
28
29 }else if( $this->mSessionKey && !$param['done']){
30 //this is a chunk piece
31 $this->chunk_mode = UploadFromChunks::CHUNK;
32 }else if( $this->mSessionKey && $param['done']){
33 //this is the last chunk
34 $this->chunk_mode = UploadFromChunks::DONE;
35 }
36 if( $this->chunk_mode == UploadFromChunks::CHUNK ||
37 $this->chunk_mode == UploadFromChunks::DONE ){
38 //set chunk related vars:
39 $this->mTempPath = $request->getFileTempName( 'chunk' );
40 $this->mFileSize = $request->getFileSize( 'chunk' );
41 }
42
43 return $this->status;
44 }
45
46 static function isValidRequest( $request ) {
47 $sessionData = $request->getSessionData('wsUploadData');
48 if(! self::isValidSessionKey(
49 $request->getInt( 'wpSessionKey' ),
50 $sessionData) )
51 return false;
52 //check for the file:
53 return (bool)$request->getFileTempName( 'file' );
54 }
55
56 /* check warnings depending on chunk_mode*/
57 function checkWarnings(){
58 $warning = array();
59 return $warning;
60 }
61
62 function isEmptyFile(){
63 //does not apply to chunk init
64 if( $this->chunk_mode == UploadFromChunks::INIT ){
65 return false;
66 }else{
67 return parent::isEmptyFile();
68 }
69 }
70 /* Verify whether the upload is sane.
71 * Returns self::OK or else an array with error information
72 */
73 function verifyUpload( $resultDetails ) {
74 //no checks on chunk upload mode:
75 if( $this->chunk_mode == UploadFromChunks::INIT )
76 return self::OK;
77
78 //verify on init and last chunk request
79 if( $this->chunk_mode == UploadFromChunks::CHUNK ||
80 $this->chunk_mode == UploadFromChunks::DONE )
81 return parent::verifyUpload( $resultDetails );
82 }
83 //only run verifyFile on completed uploaded chunks
84 function verifyFile( $tmpFile ){
85 if( $this->chunk_mode == UploadFromChunks::DONE ){
86 //first append last chunk (so we can do a real verifyFile check... (check file type etc)
87 $status = $this->doChunkAppend();
88 if( $status->isOK() ){
89 $this->mTempPath = $this->getRealPath( $this->mTempAppendPath );
90 //verify the completed merged chunks as if it was the file that got uploaded:
91 return parent::verifyFile( $this->mTempPath ) ;
92 }else{
93 //conflict of status returns (have to return the error ary) ... why we don't consistantly use a status object is beyond me..
94 return $status->getErrorsArray();
95 }
96 }else{
97 return true;
98 }
99 }
100 function getRealPath($srcPath){
101 $repo = RepoGroup::singleton()->getLocalRepo();
102 if ( $repo->isVirtualUrl( $srcPath) ) {
103 return $repo->resolveVirtualUrl( $srcPath );
104 }
105 }
106 //pretty ugly inter-mixing of mParam and local vars
107 function setupChunkSession( $summary, $comment, $watch ) {
108 $this->mSessionKey = $this->getSessionKey();
109 $_SESSION['wsUploadData'][ $this->mSessionKey ] = array(
110 'mComment' => $comment,
111 'mSummary' => $summary,
112 'mWatch' => $watch,
113 'mFilteredName' => $this->mFilteredName,
114 'mTempAppendPath' => null, //the repo append path (not temporary local node mTempPath)
115 'mDesiredDestName' => $this->mDesiredDestName,
116 'version' => self::SESSION_VERSION,
117 );
118 return $this->mSessionKey;
119 }
120 function initFromSessionKey( $sessionKey, $request ){
121 if( !$sessionKey || empty( $sessionKey ) ){
122 return false;
123 }
124 $this->mSessionKey = $sessionKey;
125 //load the sessionData array:
126 $sessionData = $request->getSessionData('wsUploadData');
127
128 if( isset( $sessionData[$this->mSessionKey]['version'] ) &&
129 $sessionData[$this->mSessionKey]['version'] == self::SESSION_VERSION ) {
130 //update the local object from the session //
131 $this->mComment = $sessionData[ $this->mSessionKey ][ 'mComment' ];
132 $this->mSummary = $sessionData[ $this->mSessionKey ][ 'mSummary' ];
133 $this->mWatch = $sessionData[ $this->mSessionKey ][ 'mWatch' ];
134 $this->mIgnorewarnings = $sessionData[ $this->mSessionKey ][ 'mIgnorewarnings' ];
135 $this->mFilteredName = $sessionData[ $this->mSessionKey ][ 'mFilteredName' ];
136 $this->mTempAppendPath = $sessionData[ $this->mSessionKey ][ 'mTempAppendPath' ];
137 $this->mDesiredDestName = $sessionData[ $this->mSessionKey ][ 'mDesiredDestName' ];
138 }else{
139 $this->status = Array( 'error'=> 'missing session data');
140 return false;
141 }
142 }
143 //lets us return an api result (as flow for chunk uploads is kind of different than others.
144 function performUpload($summary='', $comment='', $watch='', $user){
145 global $wgServer, $wgScriptPath, $wgUser;
146 if( $this->chunk_mode == UploadFromChunks::INIT ){
147 //firefogg expects a specific result per:
148 //http://www.firefogg.org/dev/chunk_post.html
149
150 //its oky to return the token here because
151 //a) the user must have requested the token to get here and
152 //b) should only happen over POST
153 //c) (we need the token to validate chunks are coming from a non-xss request)
154 $token = urlencode( $wgUser->editToken() );
155 ob_clean();
156 echo ApiFormatJson::getJsonEncode( array(
157 "uploadUrl" => "{$wgServer}{$wgScriptPath}/api.php?action=upload&".
158 "token={$token}&format=json&enablechunks=true&chunksessionkey=".
159 $this->setupChunkSession($summary, $comment, $watch ) ) );
160 exit(0);
161 }else if( $this->chunk_mode == UploadFromChunks::CHUNK ){
162 $status = $this->doChunkAppend();
163 if( $status->isOK() ){
164 //return success:
165 //firefogg expects a specific result per:
166 //http://www.firefogg.org/dev/chunk_post.html
167 ob_clean();
168 echo ApiFormatJson::getJsonEncode( array(
169 "result"=>1,
170 "filesize"=> filesize( $this->getRealPath( $this->mTempAppendPath ) )
171 )
172 );
173 exit(0);
174 /*return array(
175 'result' => 1
176 );*/
177 }else{
178 return $status;
179 }
180 }else if( $this->chunk_mode == UploadFromChunks::DONE ){
181 //update the values from the local (session init) if not paseed again)
182 if($summary == '')
183 $summary = $this->mSummary;
184
185 if($comment == '')
186 $comment = $this->mComment;
187
188 if($watch == '')
189 $watch = $this->mWatch;
190 $status = parent::performUpload($summary, $comment, $watch, $user );
191 if( !$status->isGood() ) {
192 return $status;
193 }
194 $file = $this->getLocalFile();
195 //firefogg expects a specific result per:
196 //http://www.firefogg.org/dev/chunk_post.html
197 ob_clean();
198 echo ApiFormatJson::getJsonEncode( array(
199 "result"=>1,
200 "done"=>1,
201 "resultUrl"=> $file->getDescriptionUrl()
202 )
203 );
204 exit(0);
205
206 }
207 }
208 //append the given chunk to the temporary uploaded file. (if no temporary uploaded file exists created it.
209 function doChunkAppend(){
210 //if we don't have a mTempAppendPath to generate a file from the chunk packaged var:
211 if( ! $this->mTempAppendPath ){
212 //die();
213 //get temp name:
214 //make a chunk store path. (append tmp file to chunk)
215 $status = $this->saveTempUploadedFile( $this->mDestName, $this->mTempPath );
216
217 if( $status->isOK() ) {
218 $this->mTempAppendPath = $status->value;
219 $_SESSION[ 'wsUploadData' ][ $this->mSessionKey ][ 'mTempAppendPath' ] = $this->mTempAppendPath;
220 }
221 return $status;
222 }else{
223 if( is_file( $this->getRealPath( $this->mTempAppendPath ) ) ){
224 $status = $this->appendToUploadFile( $this->mTempAppendPath, $this->mTempPath );
225 }else{
226 $status->fatal( 'filenotfound', $this->mTempAppendPath );
227 }
228 return $status;
229 }
230 }
231 }