* (bug 20336) changed json_decode json_encode to static class in global functions
[lhc/web/wiklou.git] / includes / upload / UploadFromChunks.php
1 <?php
2 /**
3 * @file
4 * @ingroup upload
5 *
6 * @author Michael Dale
7 *
8 * first destination checks are made (if ignorewarnings is not checked) errors / warning is returned.
9 *
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
15 */
16 class UploadFromChunks extends UploadBase {
17
18 var $chunk_mode; // init, chunk, done
19 var $mSessionKey = false;
20 var $status = array();
21
22 const INIT = 1;
23 const CHUNK = 2;
24 const DONE = 3;
25 public function initializeFromRequest( &$request ){
26 //should merge initializeFromParams (but just needs to be working atm)
27 }
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;
41 }
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' );
47 }
48
49 return $this->status;
50 }
51 static function isValidRequest( $request ) {
52 $sessionData = $request->getSessionData( 'wsUploadData' );
53 if( !self::isValidSessionKey(
54 $request->getInt( 'wpSessionKey' ),
55 $sessionData ) )
56 return false;
57 // check for the file:
58 return (bool)$request->getFileTempName( 'file' );
59 }
60
61 /* check warnings depending on chunk_mode */
62 function checkWarnings(){
63 $warning = array();
64 return $warning;
65 }
66
67 function isEmptyFile(){
68 // does not apply to chunk init
69 if( $this->chunk_mode == UploadFromChunks::INIT ){
70 return false;
71 } else {
72 return parent::isEmptyFile();
73 }
74 }
75
76 /**
77 * Verify whether the upload is sane.
78 * Returns self::OK or else an array with error information
79 */
80 function verifyUpload() {
81 // no checks on chunk upload mode:
82 if( $this->chunk_mode == UploadFromChunks::INIT )
83 return array( 'status' => self::OK );
84
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();
89 }
90
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 );
100 } else {
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();
103 }
104 } else {
105 return true;
106 }
107 }
108
109 function getRealPath( $srcPath ){
110 $repo = RepoGroup::singleton()->getLocalRepo();
111 if ( $repo->isVirtualUrl( $srcPath ) ) {
112 return $repo->resolveVirtualUrl( $srcPath );
113 }
114 }
115
116 // pretty ugly inter-mixing of mParam and local vars
117 function setupChunkSession( $summary, $comment, $watch ) {
118 $this->mSessionKey = $this->getSessionKey();
119 $_SESSION['wsUploadData'][$this->mSessionKey] = array(
120 'mComment' => $comment,
121 'mSummary' => $summary,
122 'mWatch' => $watch,
123 'mIgnorewarnings' => true, //ignore warning on chunk uploads (for now)
124 'mFilteredName' => $this->mFilteredName,
125 'mTempAppendPath' => null, // the repo append path (not temporary local node mTempPath)
126 'mDesiredDestName' => $this->mDesiredDestName,
127 'version' => self::SESSION_VERSION,
128 );
129 return $this->mSessionKey;
130 }
131
132 function initFromSessionKey( $sessionKey, $request ){
133 if( !$sessionKey || empty( $sessionKey ) ){
134 return false;
135 }
136 $this->mSessionKey = $sessionKey;
137 // load the sessionData array:
138 $sessionData = $request->getSessionData( 'wsUploadData' );
139
140 if( isset( $sessionData[$this->mSessionKey]['version'] ) &&
141 $sessionData[$this->mSessionKey]['version'] == self::SESSION_VERSION ) {
142 // update the local object from the session
143 $this->mComment = $sessionData[$this->mSessionKey]['mComment'];
144 $this->mSummary = $sessionData[$this->mSessionKey]['mSummary'];
145 $this->mWatch = $sessionData[$this->mSessionKey]['mWatch'];
146 $this->mIgnorewarnings = $sessionData[$this->mSessionKey]['mIgnorewarnings'];
147 $this->mFilteredName = $sessionData[$this->mSessionKey]['mFilteredName'];
148 $this->mTempAppendPath = $sessionData[$this->mSessionKey]['mTempAppendPath'];
149 $this->mDesiredDestName = $sessionData[$this->mSessionKey]['mDesiredDestName'];
150 } else {
151 $this->status = array( 'error' => 'missing session data' );
152 return false;
153 }
154 }
155
156 // Lets us return an api result (as flow for chunk uploads is kind of different than others.
157 function performUpload( $summary = '', $comment = '', $watch = '', $user ){
158 global $wgUser;
159
160 if( $this->chunk_mode == UploadFromChunks::INIT ){
161 // firefogg expects a specific result per:
162 // http://www.firefogg.org/dev/chunk_post.html
163
164 // it's okay to return the token here because
165 // a) the user must have requested the token to get here and
166 // b) should only happen over POST
167 // c) (we need the token to validate chunks are coming from a non-xss request)
168 $token = urlencode( $wgUser->editToken() );
169 ob_clean();
170 echo FormatJson::encode( array(
171 'uploadUrl' => wfExpandUrl( wfScript( 'api' ) ) . "?action=upload&".
172 "token={$token}&format=json&enablechunks=true&chunksessionkey=".
173 $this->setupChunkSession( $summary, $comment, $watch ) ) );
174 exit( 0 );
175 } else if( $this->chunk_mode == UploadFromChunks::CHUNK ){
176 $status = $this->doChunkAppend();
177 if( $status->isOK() ){
178 // return success:
179 // firefogg expects a specific result per:
180 // http://www.firefogg.org/dev/chunk_post.html
181 ob_clean();
182 echo FormatJson::encode( array(
183 'result' => 1,
184 'filesize' => filesize( $this->getRealPath( $this->mTempAppendPath ) )
185 )
186 );
187 exit( 0 );
188 /*return array(
189 'result' => 1
190 );*/
191 } else {
192 return $status;
193 }
194 } else if( $this->chunk_mode == UploadFromChunks::DONE ){
195 // update the values from the local (session init) if not paseed again)
196 if( $summary == '' )
197 $summary = $this->mSummary;
198
199 if( $comment == '' )
200 $comment = $this->mComment;
201
202 if( $watch == '' )
203 $watch = $this->mWatch;
204 $status = parent::performUpload( $summary, $comment, $watch, $user );
205 if( !$status->isGood() ) {
206 return $status;
207 }
208 $file = $this->getLocalFile();
209 // firefogg expects a specific result per:
210 // http://www.firefogg.org/dev/chunk_post.html
211 ob_clean();
212 echo FormatJson::encode( array(
213 'result' => 1,
214 'done' => 1,
215 'resultUrl' => $file->getDescriptionUrl()
216 )
217 );
218 exit( 0 );
219
220 }
221 }
222
223 // append the given chunk to the temporary uploaded file. (if no temporary uploaded file exists created it.
224 function doChunkAppend(){
225 global $wgMaxUploadSize;
226 // if we don't have a mTempAppendPath to generate a file from the chunk packaged var:
227 if( !$this->mTempAppendPath ){
228 // get temp name:
229 // make a chunk store path. (append tmp file to chunk)
230 $status = $this->saveTempUploadedFile( $this->mDestName, $this->mTempPath );
231
232 if( $status->isOK() ) {
233 $this->mTempAppendPath = $status->value;
234 $_SESSION['wsUploadData'][$this->mSessionKey]['mTempAppendPath'] = $this->mTempAppendPath;
235 }
236 return $status;
237 } else {
238 if( is_file( $this->getRealPath( $this->mTempAppendPath ) ) ){
239 $status = $this->appendToUploadFile( $this->mTempAppendPath, $this->mTempPath );
240 } else {
241 $status = Status::newFatal( 'filenotfound', $this->mTempAppendPath );
242 }
243 //check to make sure we have not expanded beyond $wgMaxUploadSize
244 if( filesize( $this->getRealPath( $this->mTempAppendPath ) ) > $wgMaxUploadSize )
245 $status = Status::newFatal( 'largefileserver' );
246
247 return $status;
248 }
249 }
250
251 }