These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Temporary storage for uploaded files. |
||
4 | * |
||
5 | * This program is free software; you can redistribute it and/or modify |
||
6 | * it under the terms of the GNU General Public License as published by |
||
7 | * the Free Software Foundation; either version 2 of the License, or |
||
8 | * (at your option) any later version. |
||
9 | * |
||
10 | * This program is distributed in the hope that it will be useful, |
||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
13 | * GNU General Public License for more details. |
||
14 | * |
||
15 | * You should have received a copy of the GNU General Public License along |
||
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
18 | * http://www.gnu.org/copyleft/gpl.html |
||
19 | * |
||
20 | * @file |
||
21 | * @ingroup Upload |
||
22 | */ |
||
23 | |||
24 | /** |
||
25 | * UploadStash is intended to accomplish a few things: |
||
26 | * - Enable applications to temporarily stash files without publishing them to |
||
27 | * the wiki. |
||
28 | * - Several parts of MediaWiki do this in similar ways: UploadBase, |
||
29 | * UploadWizard, and FirefoggChunkedExtension. |
||
30 | * And there are several that reimplement stashing from scratch, in |
||
31 | * idiosyncratic ways. The idea is to unify them all here. |
||
32 | * Mostly all of them are the same except for storing some custom fields, |
||
33 | * which we subsume into the data array. |
||
34 | * - Enable applications to find said files later, as long as the db table or |
||
35 | * temp files haven't been purged. |
||
36 | * - Enable the uploading user (and *ONLY* the uploading user) to access said |
||
37 | * files, and thumbnails of said files, via a URL. We accomplish this using |
||
38 | * a database table, with ownership checking as you might expect. See |
||
39 | * SpecialUploadStash, which implements a web interface to some files stored |
||
40 | * this way. |
||
41 | * |
||
42 | * UploadStash right now is *mostly* intended to show you one user's slice of |
||
43 | * the entire stash. The user parameter is only optional because there are few |
||
44 | * cases where we clean out the stash from an automated script. In the future we |
||
45 | * might refactor this. |
||
46 | * |
||
47 | * UploadStash represents the entire stash of temporary files. |
||
48 | * UploadStashFile is a filestore for the actual physical disk files. |
||
49 | * UploadFromStash extends UploadBase, and represents a single stashed file as |
||
50 | * it is moved from the stash to the regular file repository |
||
51 | * |
||
52 | * @ingroup Upload |
||
53 | */ |
||
54 | class UploadStash { |
||
55 | // Format of the key for files -- has to be suitable as a filename itself (e.g. ab12cd34ef.jpg) |
||
56 | const KEY_FORMAT_REGEX = '/^[\w-\.]+\.\w*$/'; |
||
57 | const MAX_US_PROPS_SIZE = 65535; |
||
58 | |||
59 | /** |
||
60 | * repository that this uses to store temp files |
||
61 | * public because we sometimes need to get a LocalFile within the same repo. |
||
62 | * |
||
63 | * @var LocalRepo |
||
64 | */ |
||
65 | public $repo; |
||
66 | |||
67 | // array of initialized repo objects |
||
68 | protected $files = []; |
||
69 | |||
70 | // cache of the file metadata that's stored in the database |
||
71 | protected $fileMetadata = []; |
||
72 | |||
73 | // fileprops cache |
||
74 | protected $fileProps = []; |
||
75 | |||
76 | // current user |
||
77 | protected $user, $userId, $isLoggedIn; |
||
0 ignored issues
–
show
|
|||
78 | |||
79 | /** |
||
80 | * Represents a temporary filestore, with metadata in the database. |
||
81 | * Designed to be compatible with the session stashing code in UploadBase |
||
82 | * (should replace it eventually). |
||
83 | * |
||
84 | * @param FileRepo $repo |
||
85 | * @param User $user (default null) |
||
86 | */ |
||
87 | public function __construct( FileRepo $repo, $user = null ) { |
||
88 | // this might change based on wiki's configuration. |
||
89 | $this->repo = $repo; |
||
90 | |||
91 | // if a user was passed, use it. otherwise, attempt to use the global. |
||
92 | // this keeps FileRepo from breaking when it creates an UploadStash object |
||
93 | if ( $user ) { |
||
94 | $this->user = $user; |
||
95 | } else { |
||
96 | global $wgUser; |
||
97 | $this->user = $wgUser; |
||
98 | } |
||
99 | |||
100 | if ( is_object( $this->user ) ) { |
||
101 | $this->userId = $this->user->getId(); |
||
102 | $this->isLoggedIn = $this->user->isLoggedIn(); |
||
103 | } |
||
104 | } |
||
105 | |||
106 | /** |
||
107 | * Get a file and its metadata from the stash. |
||
108 | * The noAuth param is a bit janky but is required for automated scripts |
||
109 | * which clean out the stash. |
||
110 | * |
||
111 | * @param string $key Key under which file information is stored |
||
112 | * @param bool $noAuth (optional) Don't check authentication. Used by maintenance scripts. |
||
113 | * @throws UploadStashFileNotFoundException |
||
114 | * @throws UploadStashNotLoggedInException |
||
115 | * @throws UploadStashWrongOwnerException |
||
116 | * @throws UploadStashBadPathException |
||
117 | * @return UploadStashFile |
||
118 | */ |
||
119 | public function getFile( $key, $noAuth = false ) { |
||
120 | if ( !preg_match( self::KEY_FORMAT_REGEX, $key ) ) { |
||
121 | throw new UploadStashBadPathException( "key '$key' is not in a proper format" ); |
||
122 | } |
||
123 | |||
124 | if ( !$noAuth && !$this->isLoggedIn ) { |
||
125 | throw new UploadStashNotLoggedInException( __METHOD__ . |
||
126 | ' No user is logged in, files must belong to users' ); |
||
127 | } |
||
128 | |||
129 | if ( !isset( $this->fileMetadata[$key] ) ) { |
||
130 | if ( !$this->fetchFileMetadata( $key ) ) { |
||
131 | // If nothing was received, it's likely due to replication lag. |
||
132 | // Check the master to see if the record is there. |
||
133 | $this->fetchFileMetadata( $key, DB_MASTER ); |
||
134 | } |
||
135 | |||
136 | if ( !isset( $this->fileMetadata[$key] ) ) { |
||
137 | throw new UploadStashFileNotFoundException( "key '$key' not found in stash" ); |
||
138 | } |
||
139 | |||
140 | // create $this->files[$key] |
||
141 | $this->initFile( $key ); |
||
142 | |||
143 | // fetch fileprops |
||
144 | if ( strlen( $this->fileMetadata[$key]['us_props'] ) ) { |
||
145 | $this->fileProps[$key] = unserialize( $this->fileMetadata[$key]['us_props'] ); |
||
146 | } else { // b/c for rows with no us_props |
||
147 | wfDebug( __METHOD__ . " fetched props for $key from file\n" ); |
||
148 | $path = $this->fileMetadata[$key]['us_path']; |
||
149 | $this->fileProps[$key] = $this->repo->getFileProps( $path ); |
||
150 | } |
||
151 | } |
||
152 | |||
153 | if ( !$this->files[$key]->exists() ) { |
||
154 | wfDebug( __METHOD__ . " tried to get file at $key, but it doesn't exist\n" ); |
||
155 | // @todo Is this not an UploadStashFileNotFoundException case? |
||
156 | throw new UploadStashBadPathException( "path doesn't exist" ); |
||
157 | } |
||
158 | |||
159 | if ( !$noAuth ) { |
||
160 | if ( $this->fileMetadata[$key]['us_user'] != $this->userId ) { |
||
161 | throw new UploadStashWrongOwnerException( "This file ($key) doesn't " |
||
162 | . "belong to the current user." ); |
||
163 | } |
||
164 | } |
||
165 | |||
166 | return $this->files[$key]; |
||
167 | } |
||
168 | |||
169 | /** |
||
170 | * Getter for file metadata. |
||
171 | * |
||
172 | * @param string $key Key under which file information is stored |
||
173 | * @return array |
||
174 | */ |
||
175 | public function getMetadata( $key ) { |
||
176 | $this->getFile( $key ); |
||
177 | |||
178 | return $this->fileMetadata[$key]; |
||
179 | } |
||
180 | |||
181 | /** |
||
182 | * Getter for fileProps |
||
183 | * |
||
184 | * @param string $key Key under which file information is stored |
||
185 | * @return array |
||
186 | */ |
||
187 | public function getFileProps( $key ) { |
||
188 | $this->getFile( $key ); |
||
189 | |||
190 | return $this->fileProps[$key]; |
||
191 | } |
||
192 | |||
193 | /** |
||
194 | * Stash a file in a temp directory and record that we did this in the |
||
195 | * database, along with other metadata. |
||
196 | * |
||
197 | * @param string $path Path to file you want stashed |
||
198 | * @param string $sourceType The type of upload that generated this file |
||
199 | * (currently, I believe, 'file' or null) |
||
200 | * @throws UploadStashBadPathException |
||
201 | * @throws UploadStashFileException |
||
202 | * @throws UploadStashNotLoggedInException |
||
203 | * @return UploadStashFile|null File, or null on failure |
||
204 | */ |
||
205 | public function stashFile( $path, $sourceType = null ) { |
||
206 | if ( !is_file( $path ) ) { |
||
207 | wfDebug( __METHOD__ . " tried to stash file at '$path', but it doesn't exist\n" ); |
||
208 | throw new UploadStashBadPathException( "path doesn't exist" ); |
||
209 | } |
||
210 | |||
211 | $mwProps = new MWFileProps( MimeMagic::singleton() ); |
||
212 | $fileProps = $mwProps->getPropsFromPath( $path, true ); |
||
213 | wfDebug( __METHOD__ . " stashing file at '$path'\n" ); |
||
214 | |||
215 | // we will be initializing from some tmpnam files that don't have extensions. |
||
216 | // most of MediaWiki assumes all uploaded files have good extensions. So, we fix this. |
||
217 | $extension = self::getExtensionForPath( $path ); |
||
218 | if ( !preg_match( "/\\.\\Q$extension\\E$/", $path ) ) { |
||
219 | $pathWithGoodExtension = "$path.$extension"; |
||
220 | } else { |
||
221 | $pathWithGoodExtension = $path; |
||
222 | } |
||
223 | |||
224 | // If no key was supplied, make one. a mysql insertid would be totally |
||
225 | // reasonable here, except that for historical reasons, the key is this |
||
226 | // random thing instead. At least it's not guessable. |
||
227 | // Some things that when combined will make a suitably unique key. |
||
228 | // see: http://www.jwz.org/doc/mid.html |
||
229 | list( $usec, $sec ) = explode( ' ', microtime() ); |
||
230 | $usec = substr( $usec, 2 ); |
||
231 | $key = Wikimedia\base_convert( $sec . $usec, 10, 36 ) . '.' . |
||
232 | Wikimedia\base_convert( mt_rand(), 10, 36 ) . '.' . |
||
233 | $this->userId . '.' . |
||
234 | $extension; |
||
235 | |||
236 | $this->fileProps[$key] = $fileProps; |
||
237 | |||
238 | if ( !preg_match( self::KEY_FORMAT_REGEX, $key ) ) { |
||
239 | throw new UploadStashBadPathException( "key '$key' is not in a proper format" ); |
||
240 | } |
||
241 | |||
242 | wfDebug( __METHOD__ . " key for '$path': $key\n" ); |
||
243 | |||
244 | // if not already in a temporary area, put it there |
||
245 | $storeStatus = $this->repo->storeTemp( basename( $pathWithGoodExtension ), $path ); |
||
246 | |||
247 | if ( !$storeStatus->isOK() ) { |
||
248 | // It is a convention in MediaWiki to only return one error per API |
||
249 | // exception, even if multiple errors are available. We use reset() |
||
250 | // to pick the "first" thing that was wrong, preferring errors to |
||
251 | // warnings. This is a bit lame, as we may have more info in the |
||
252 | // $storeStatus and we're throwing it away, but to fix it means |
||
253 | // redesigning API errors significantly. |
||
254 | // $storeStatus->value just contains the virtual URL (if anything) |
||
255 | // which is probably useless to the caller. |
||
256 | $error = $storeStatus->getErrorsArray(); |
||
257 | $error = reset( $error ); |
||
258 | View Code Duplication | if ( !count( $error ) ) { |
|
259 | $error = $storeStatus->getWarningsArray(); |
||
260 | $error = reset( $error ); |
||
261 | if ( !count( $error ) ) { |
||
262 | $error = [ 'unknown', 'no error recorded' ]; |
||
263 | } |
||
264 | } |
||
265 | // At this point, $error should contain the single "most important" |
||
266 | // error, plus any parameters. |
||
267 | $errorMsg = array_shift( $error ); |
||
268 | throw new UploadStashFileException( "Error storing file in '$path': " |
||
269 | . wfMessage( $errorMsg, $error )->text() ); |
||
270 | } |
||
271 | $stashPath = $storeStatus->value; |
||
272 | |||
273 | // fetch the current user ID |
||
274 | if ( !$this->isLoggedIn ) { |
||
275 | throw new UploadStashNotLoggedInException( __METHOD__ |
||
276 | . ' No user is logged in, files must belong to users' ); |
||
277 | } |
||
278 | |||
279 | // insert the file metadata into the db. |
||
280 | wfDebug( __METHOD__ . " inserting $stashPath under $key\n" ); |
||
281 | $dbw = $this->repo->getMasterDB(); |
||
282 | |||
283 | $serializedFileProps = serialize( $fileProps ); |
||
284 | if ( strlen( $serializedFileProps ) > self::MAX_US_PROPS_SIZE ) { |
||
285 | // Database is going to truncate this and make the field invalid. |
||
286 | // Prioritize important metadata over file handler metadata. |
||
287 | // File handler should be prepared to regenerate invalid metadata if needed. |
||
288 | $fileProps['metadata'] = false; |
||
289 | $serializedFileProps = serialize( $fileProps ); |
||
290 | } |
||
291 | |||
292 | $this->fileMetadata[$key] = [ |
||
293 | 'us_id' => $dbw->nextSequenceValue( 'uploadstash_us_id_seq' ), |
||
294 | 'us_user' => $this->userId, |
||
295 | 'us_key' => $key, |
||
296 | 'us_orig_path' => $path, |
||
297 | 'us_path' => $stashPath, // virtual URL |
||
298 | 'us_props' => $dbw->encodeBlob( $serializedFileProps ), |
||
299 | 'us_size' => $fileProps['size'], |
||
300 | 'us_sha1' => $fileProps['sha1'], |
||
301 | 'us_mime' => $fileProps['mime'], |
||
302 | 'us_media_type' => $fileProps['media_type'], |
||
303 | 'us_image_width' => $fileProps['width'], |
||
304 | 'us_image_height' => $fileProps['height'], |
||
305 | 'us_image_bits' => $fileProps['bits'], |
||
306 | 'us_source_type' => $sourceType, |
||
307 | 'us_timestamp' => $dbw->timestamp(), |
||
308 | 'us_status' => 'finished' |
||
309 | ]; |
||
310 | |||
311 | $dbw->insert( |
||
312 | 'uploadstash', |
||
313 | $this->fileMetadata[$key], |
||
314 | __METHOD__ |
||
315 | ); |
||
316 | |||
317 | // store the insertid in the class variable so immediate retrieval |
||
318 | // (possibly laggy) isn't necesary. |
||
319 | $this->fileMetadata[$key]['us_id'] = $dbw->insertId(); |
||
320 | |||
321 | # create the UploadStashFile object for this file. |
||
322 | $this->initFile( $key ); |
||
323 | |||
324 | return $this->getFile( $key ); |
||
325 | } |
||
326 | |||
327 | /** |
||
328 | * Remove all files from the stash. |
||
329 | * Does not clean up files in the repo, just the record of them. |
||
330 | * |
||
331 | * @throws UploadStashNotLoggedInException |
||
332 | * @return bool Success |
||
333 | */ |
||
334 | public function clear() { |
||
335 | if ( !$this->isLoggedIn ) { |
||
336 | throw new UploadStashNotLoggedInException( __METHOD__ |
||
337 | . ' No user is logged in, files must belong to users' ); |
||
338 | } |
||
339 | |||
340 | wfDebug( __METHOD__ . ' clearing all rows for user ' . $this->userId . "\n" ); |
||
341 | $dbw = $this->repo->getMasterDB(); |
||
342 | $dbw->delete( |
||
343 | 'uploadstash', |
||
344 | [ 'us_user' => $this->userId ], |
||
345 | __METHOD__ |
||
346 | ); |
||
347 | |||
348 | # destroy objects. |
||
349 | $this->files = []; |
||
350 | $this->fileMetadata = []; |
||
351 | |||
352 | return true; |
||
353 | } |
||
354 | |||
355 | /** |
||
356 | * Remove a particular file from the stash. Also removes it from the repo. |
||
357 | * |
||
358 | * @param string $key |
||
359 | * @throws UploadStashNoSuchKeyException|UploadStashNotLoggedInException |
||
360 | * @throws UploadStashWrongOwnerException |
||
361 | * @return bool Success |
||
362 | */ |
||
363 | public function removeFile( $key ) { |
||
364 | if ( !$this->isLoggedIn ) { |
||
365 | throw new UploadStashNotLoggedInException( __METHOD__ |
||
366 | . ' No user is logged in, files must belong to users' ); |
||
367 | } |
||
368 | |||
369 | $dbw = $this->repo->getMasterDB(); |
||
370 | |||
371 | // this is a cheap query. it runs on the master so that this function |
||
372 | // still works when there's lag. It won't be called all that often. |
||
373 | $row = $dbw->selectRow( |
||
374 | 'uploadstash', |
||
375 | 'us_user', |
||
376 | [ 'us_key' => $key ], |
||
377 | __METHOD__ |
||
378 | ); |
||
379 | |||
380 | if ( !$row ) { |
||
381 | throw new UploadStashNoSuchKeyException( "No such key ($key), cannot remove" ); |
||
382 | } |
||
383 | |||
384 | if ( $row->us_user != $this->userId ) { |
||
385 | throw new UploadStashWrongOwnerException( "Can't delete: " |
||
386 | . "the file ($key) doesn't belong to this user." ); |
||
387 | } |
||
388 | |||
389 | return $this->removeFileNoAuth( $key ); |
||
390 | } |
||
391 | |||
392 | /** |
||
393 | * Remove a file (see removeFile), but doesn't check ownership first. |
||
394 | * |
||
395 | * @param string $key |
||
396 | * @return bool Success |
||
397 | */ |
||
398 | public function removeFileNoAuth( $key ) { |
||
399 | wfDebug( __METHOD__ . " clearing row $key\n" ); |
||
400 | |||
401 | // Ensure we have the UploadStashFile loaded for this key |
||
402 | $this->getFile( $key, true ); |
||
403 | |||
404 | $dbw = $this->repo->getMasterDB(); |
||
405 | |||
406 | $dbw->delete( |
||
407 | 'uploadstash', |
||
408 | [ 'us_key' => $key ], |
||
409 | __METHOD__ |
||
410 | ); |
||
411 | |||
412 | /** @todo Look into UnregisteredLocalFile and find out why the rv here is |
||
413 | * sometimes wrong (false when file was removed). For now, ignore. |
||
414 | */ |
||
415 | $this->files[$key]->remove(); |
||
416 | |||
417 | unset( $this->files[$key] ); |
||
418 | unset( $this->fileMetadata[$key] ); |
||
419 | |||
420 | return true; |
||
421 | } |
||
422 | |||
423 | /** |
||
424 | * List all files in the stash. |
||
425 | * |
||
426 | * @throws UploadStashNotLoggedInException |
||
427 | * @return array |
||
428 | */ |
||
429 | public function listFiles() { |
||
430 | if ( !$this->isLoggedIn ) { |
||
431 | throw new UploadStashNotLoggedInException( __METHOD__ |
||
432 | . ' No user is logged in, files must belong to users' ); |
||
433 | } |
||
434 | |||
435 | $dbr = $this->repo->getSlaveDB(); |
||
436 | $res = $dbr->select( |
||
437 | 'uploadstash', |
||
438 | 'us_key', |
||
439 | [ 'us_user' => $this->userId ], |
||
440 | __METHOD__ |
||
441 | ); |
||
442 | |||
443 | if ( !is_object( $res ) || $res->numRows() == 0 ) { |
||
444 | // nothing to do. |
||
445 | return false; |
||
446 | } |
||
447 | |||
448 | // finish the read before starting writes. |
||
449 | $keys = []; |
||
450 | foreach ( $res as $row ) { |
||
451 | array_push( $keys, $row->us_key ); |
||
452 | } |
||
453 | |||
454 | return $keys; |
||
455 | } |
||
456 | |||
457 | /** |
||
458 | * Find or guess extension -- ensuring that our extension matches our MIME type. |
||
459 | * Since these files are constructed from php tempnames they may not start off |
||
460 | * with an extension. |
||
461 | * XXX this is somewhat redundant with the checks that ApiUpload.php does with incoming |
||
462 | * uploads versus the desired filename. Maybe we can get that passed to us... |
||
463 | * @param string $path |
||
464 | * @throws UploadStashFileException |
||
465 | * @return string |
||
466 | */ |
||
467 | public static function getExtensionForPath( $path ) { |
||
468 | global $wgFileBlacklist; |
||
469 | // Does this have an extension? |
||
470 | $n = strrpos( $path, '.' ); |
||
471 | $extension = null; |
||
472 | if ( $n !== false ) { |
||
473 | $extension = $n ? substr( $path, $n + 1 ) : ''; |
||
474 | } else { |
||
475 | // If not, assume that it should be related to the MIME type of the original file. |
||
476 | $magic = MimeMagic::singleton(); |
||
477 | $mimeType = $magic->guessMimeType( $path ); |
||
478 | $extensions = explode( ' ', MimeMagic::singleton()->getExtensionsForType( $mimeType ) ); |
||
479 | if ( count( $extensions ) ) { |
||
480 | $extension = $extensions[0]; |
||
481 | } |
||
482 | } |
||
483 | |||
484 | if ( is_null( $extension ) ) { |
||
485 | throw new UploadStashFileException( "extension is null" ); |
||
486 | } |
||
487 | |||
488 | $extension = File::normalizeExtension( $extension ); |
||
489 | if ( in_array( $extension, $wgFileBlacklist ) ) { |
||
490 | // The file should already be checked for being evil. |
||
491 | // However, if somehow we got here, we definitely |
||
492 | // don't want to give it an extension of .php and |
||
493 | // put it in a web accesible directory. |
||
494 | return ''; |
||
495 | } |
||
496 | |||
497 | return $extension; |
||
498 | } |
||
499 | |||
500 | /** |
||
501 | * Helper function: do the actual database query to fetch file metadata. |
||
502 | * |
||
503 | * @param string $key |
||
504 | * @param int $readFromDB Constant (default: DB_REPLICA) |
||
505 | * @return bool |
||
506 | */ |
||
507 | protected function fetchFileMetadata( $key, $readFromDB = DB_REPLICA ) { |
||
508 | // populate $fileMetadata[$key] |
||
509 | $dbr = null; |
||
0 ignored issues
–
show
$dbr is not used, you could remove the assignment.
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently. $myVar = 'Value';
$higher = false;
if (rand(1, 6) > 3) {
$higher = true;
} else {
$higher = false;
}
Both the ![]() |
|||
510 | if ( $readFromDB === DB_MASTER ) { |
||
511 | // sometimes reading from the master is necessary, if there's replication lag. |
||
512 | $dbr = $this->repo->getMasterDB(); |
||
513 | } else { |
||
514 | $dbr = $this->repo->getSlaveDB(); |
||
515 | } |
||
516 | |||
517 | $row = $dbr->selectRow( |
||
518 | 'uploadstash', |
||
519 | '*', |
||
520 | [ 'us_key' => $key ], |
||
521 | __METHOD__ |
||
522 | ); |
||
523 | |||
524 | if ( !is_object( $row ) ) { |
||
525 | // key wasn't present in the database. this will happen sometimes. |
||
526 | return false; |
||
527 | } |
||
528 | |||
529 | $this->fileMetadata[$key] = (array)$row; |
||
530 | $this->fileMetadata[$key]['us_props'] = $dbr->decodeBlob( $row->us_props ); |
||
531 | |||
532 | return true; |
||
533 | } |
||
534 | |||
535 | /** |
||
536 | * Helper function: Initialize the UploadStashFile for a given file. |
||
537 | * |
||
538 | * @param string $key Key under which to store the object |
||
539 | * @throws UploadStashZeroLengthFileException |
||
540 | * @return bool |
||
541 | */ |
||
542 | protected function initFile( $key ) { |
||
543 | $file = new UploadStashFile( $this->repo, $this->fileMetadata[$key]['us_path'], $key ); |
||
544 | if ( $file->getSize() === 0 ) { |
||
545 | throw new UploadStashZeroLengthFileException( "File is zero length" ); |
||
546 | } |
||
547 | $this->files[$key] = $file; |
||
548 | |||
549 | return true; |
||
550 | } |
||
551 | } |
||
552 | |||
553 | class UploadStashFile extends UnregisteredLocalFile { |
||
554 | private $fileKey; |
||
555 | private $urlName; |
||
556 | protected $url; |
||
557 | |||
558 | /** |
||
559 | * A LocalFile wrapper around a file that has been temporarily stashed, |
||
560 | * so we can do things like create thumbnails for it. Arguably |
||
561 | * UnregisteredLocalFile should be handling its own file repo but that |
||
562 | * class is a bit retarded currently. |
||
563 | * |
||
564 | * @param FileRepo $repo Repository where we should find the path |
||
565 | * @param string $path Path to file |
||
566 | * @param string $key Key to store the path and any stashed data under |
||
567 | * @throws UploadStashBadPathException |
||
568 | * @throws UploadStashFileNotFoundException |
||
569 | */ |
||
570 | public function __construct( $repo, $path, $key ) { |
||
571 | $this->fileKey = $key; |
||
572 | |||
573 | // resolve mwrepo:// urls |
||
574 | if ( $repo->isVirtualUrl( $path ) ) { |
||
575 | $path = $repo->resolveVirtualUrl( $path ); |
||
576 | } else { |
||
577 | // check if path appears to be sane, no parent traversals, |
||
578 | // and is in this repo's temp zone. |
||
579 | $repoTempPath = $repo->getZonePath( 'temp' ); |
||
580 | if ( ( !$repo->validateFilename( $path ) ) || |
||
581 | ( strpos( $path, $repoTempPath ) !== 0 ) |
||
582 | ) { |
||
583 | wfDebug( "UploadStash: tried to construct an UploadStashFile " |
||
584 | . "from a file that should already exist at '$path', but path is not valid\n" ); |
||
585 | throw new UploadStashBadPathException( 'path is not valid' ); |
||
586 | } |
||
587 | |||
588 | // check if path exists! and is a plain file. |
||
589 | if ( !$repo->fileExists( $path ) ) { |
||
590 | wfDebug( "UploadStash: tried to construct an UploadStashFile from " |
||
591 | . "a file that should already exist at '$path', but path is not found\n" ); |
||
592 | throw new UploadStashFileNotFoundException( 'cannot find path, or not a plain file' ); |
||
593 | } |
||
594 | } |
||
595 | |||
596 | parent::__construct( false, $repo, $path, false ); |
||
597 | |||
598 | $this->name = basename( $this->path ); |
||
599 | } |
||
600 | |||
601 | /** |
||
602 | * A method needed by the file transforming and scaling routines in File.php |
||
603 | * We do not necessarily care about doing the description at this point |
||
604 | * However, we also can't return the empty string, as the rest of MediaWiki |
||
605 | * demands this (and calls to imagemagick convert require it to be there) |
||
606 | * |
||
607 | * @return string Dummy value |
||
608 | */ |
||
609 | public function getDescriptionUrl() { |
||
610 | return $this->getUrl(); |
||
611 | } |
||
612 | |||
613 | /** |
||
614 | * Get the path for the thumbnail (actually any transformation of this file) |
||
615 | * The actual argument is the result of thumbName although we seem to have |
||
616 | * buggy code elsewhere that expects a boolean 'suffix' |
||
617 | * |
||
618 | * @param string $thumbName Name of thumbnail (e.g. "120px-123456.jpg" ), |
||
619 | * or false to just get the path |
||
620 | * @return string Path thumbnail should take on filesystem, or containing |
||
621 | * directory if thumbname is false |
||
622 | */ |
||
623 | public function getThumbPath( $thumbName = false ) { |
||
624 | $path = dirname( $this->path ); |
||
625 | if ( $thumbName !== false ) { |
||
626 | $path .= "/$thumbName"; |
||
627 | } |
||
628 | |||
629 | return $path; |
||
630 | } |
||
631 | |||
632 | /** |
||
633 | * Return the file/url base name of a thumbnail with the specified parameters. |
||
634 | * We override this because we want to use the pretty url name instead of the |
||
635 | * ugly file name. |
||
636 | * |
||
637 | * @param array $params Handler-specific parameters |
||
638 | * @param int $flags Bitfield that supports THUMB_* constants |
||
639 | * @return string|null Base name for URL, like '120px-12345.jpg', or null if there is no handler |
||
640 | */ |
||
641 | function thumbName( $params, $flags = 0 ) { |
||
642 | return $this->generateThumbName( $this->getUrlName(), $params ); |
||
643 | } |
||
644 | |||
645 | /** |
||
646 | * Helper function -- given a 'subpage', return the local URL, |
||
647 | * e.g. /wiki/Special:UploadStash/subpage |
||
648 | * @param string $subPage |
||
649 | * @return string Local URL for this subpage in the Special:UploadStash space. |
||
650 | */ |
||
651 | private function getSpecialUrl( $subPage ) { |
||
652 | return SpecialPage::getTitleFor( 'UploadStash', $subPage )->getLocalURL(); |
||
653 | } |
||
654 | |||
655 | /** |
||
656 | * Get a URL to access the thumbnail |
||
657 | * This is required because the model of how files work requires that |
||
658 | * the thumbnail urls be predictable. However, in our model the URL is |
||
659 | * not based on the filename (that's hidden in the db) |
||
660 | * |
||
661 | * @param string $thumbName Basename of thumbnail file -- however, we don't |
||
662 | * want to use the file exactly |
||
663 | * @return string URL to access thumbnail, or URL with partial path |
||
664 | */ |
||
665 | public function getThumbUrl( $thumbName = false ) { |
||
666 | wfDebug( __METHOD__ . " getting for $thumbName \n" ); |
||
667 | |||
668 | return $this->getSpecialUrl( 'thumb/' . $this->getUrlName() . '/' . $thumbName ); |
||
669 | } |
||
670 | |||
671 | /** |
||
672 | * The basename for the URL, which we want to not be related to the filename. |
||
673 | * Will also be used as the lookup key for a thumbnail file. |
||
674 | * |
||
675 | * @return string Base url name, like '120px-123456.jpg' |
||
676 | */ |
||
677 | public function getUrlName() { |
||
678 | if ( !$this->urlName ) { |
||
679 | $this->urlName = $this->fileKey; |
||
680 | } |
||
681 | |||
682 | return $this->urlName; |
||
683 | } |
||
684 | |||
685 | /** |
||
686 | * Return the URL of the file, if for some reason we wanted to download it |
||
687 | * We tend not to do this for the original file, but we do want thumb icons |
||
688 | * |
||
689 | * @return string Url |
||
690 | */ |
||
691 | public function getUrl() { |
||
692 | if ( !isset( $this->url ) ) { |
||
693 | $this->url = $this->getSpecialUrl( 'file/' . $this->getUrlName() ); |
||
694 | } |
||
695 | |||
696 | return $this->url; |
||
697 | } |
||
698 | |||
699 | /** |
||
700 | * Parent classes use this method, for no obvious reason, to return the path |
||
701 | * (relative to wiki root, I assume). But with this class, the URL is |
||
702 | * unrelated to the path. |
||
703 | * |
||
704 | * @return string Url |
||
705 | */ |
||
706 | public function getFullUrl() { |
||
707 | return $this->getUrl(); |
||
708 | } |
||
709 | |||
710 | /** |
||
711 | * Getter for file key (the unique id by which this file's location & |
||
712 | * metadata is stored in the db) |
||
713 | * |
||
714 | * @return string File key |
||
715 | */ |
||
716 | public function getFileKey() { |
||
717 | return $this->fileKey; |
||
718 | } |
||
719 | |||
720 | /** |
||
721 | * Remove the associated temporary file |
||
722 | * @return status Success |
||
723 | */ |
||
724 | public function remove() { |
||
725 | if ( !$this->repo->fileExists( $this->path ) ) { |
||
726 | // Maybe the file's already been removed? This could totally happen in UploadBase. |
||
727 | return true; |
||
728 | } |
||
729 | |||
730 | return $this->repo->freeTemp( $this->path ); |
||
731 | } |
||
732 | |||
733 | public function exists() { |
||
734 | return $this->repo->fileExists( $this->path ); |
||
735 | } |
||
736 | } |
||
737 | |||
738 | class UploadStashException extends MWException { |
||
739 | } |
||
740 | |||
741 | class UploadStashFileNotFoundException extends UploadStashException { |
||
742 | } |
||
743 | |||
744 | class UploadStashBadPathException extends UploadStashException { |
||
745 | } |
||
746 | |||
747 | class UploadStashFileException extends UploadStashException { |
||
748 | } |
||
749 | |||
750 | class UploadStashZeroLengthFileException extends UploadStashException { |
||
751 | } |
||
752 | |||
753 | class UploadStashNotLoggedInException extends UploadStashException { |
||
754 | } |
||
755 | |||
756 | class UploadStashWrongOwnerException extends UploadStashException { |
||
757 | } |
||
758 | |||
759 | class UploadStashNoSuchKeyException extends UploadStashException { |
||
760 | } |
||
761 |
Only declaring a single property per statement allows you to later on add doc comments more easily.
It is also recommended by PSR2, so it is a common style that many people expect.