Completed
Branch master (90e9fc)
by
unknown
29:23
created

UploadStash::fetchFileMetadata()   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 27
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 16
nc 4
nop 2
dl 0
loc 27
rs 8.8571
c 0
b 0
f 0
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 info
77
	protected $userId, $isLoggedIn;
0 ignored issues
show
Coding Style introduced by
It is generally advisable to only define one property per statement.

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.

Loading history...
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
86
	 */
87
	public function __construct( FileRepo $repo, User $user ) {
88
		// this might change based on wiki's configuration.
89
		$this->repo = $repo;
0 ignored issues
show
Documentation Bug introduced by
$repo is of type object<FileRepo>, but the property $repo was declared to be of type object<LocalRepo>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
90
91
		// We only need the logged in status and user id.
92
		$this->userId = $user->getId();
93
		$this->isLoggedIn = $user->isLoggedIn();
94
	}
95
96
	/**
97
	 * Get a file and its metadata from the stash.
98
	 * The noAuth param is a bit janky but is required for automated scripts
99
	 * which clean out the stash.
100
	 *
101
	 * @param string $key Key under which file information is stored
102
	 * @param bool $noAuth (optional) Don't check authentication. Used by maintenance scripts.
103
	 * @throws UploadStashFileNotFoundException
104
	 * @throws UploadStashNotLoggedInException
105
	 * @throws UploadStashWrongOwnerException
106
	 * @throws UploadStashBadPathException
107
	 * @return UploadStashFile
108
	 */
109
	public function getFile( $key, $noAuth = false ) {
110
		if ( !preg_match( self::KEY_FORMAT_REGEX, $key ) ) {
111
			throw new UploadStashBadPathException( "key '$key' is not in a proper format" );
112
		}
113
114
		if ( !$noAuth && !$this->isLoggedIn ) {
115
			throw new UploadStashNotLoggedInException( __METHOD__ .
116
				' No user is logged in, files must belong to users' );
117
		}
118
119
		if ( !isset( $this->fileMetadata[$key] ) ) {
120
			if ( !$this->fetchFileMetadata( $key ) ) {
121
				// If nothing was received, it's likely due to replication lag.
122
				// Check the master to see if the record is there.
123
				$this->fetchFileMetadata( $key, DB_MASTER );
124
			}
125
126
			if ( !isset( $this->fileMetadata[$key] ) ) {
127
				throw new UploadStashFileNotFoundException( "key '$key' not found in stash" );
128
			}
129
130
			// create $this->files[$key]
131
			$this->initFile( $key );
132
133
			// fetch fileprops
134
			if ( strlen( $this->fileMetadata[$key]['us_props'] ) ) {
135
				$this->fileProps[$key] = unserialize( $this->fileMetadata[$key]['us_props'] );
136
			} else { // b/c for rows with no us_props
137
				wfDebug( __METHOD__ . " fetched props for $key from file\n" );
138
				$path = $this->fileMetadata[$key]['us_path'];
139
				$this->fileProps[$key] = $this->repo->getFileProps( $path );
140
			}
141
		}
142
143
		if ( !$this->files[$key]->exists() ) {
144
			wfDebug( __METHOD__ . " tried to get file at $key, but it doesn't exist\n" );
145
			// @todo Is this not an UploadStashFileNotFoundException case?
146
			throw new UploadStashBadPathException( "path doesn't exist" );
147
		}
148
149
		if ( !$noAuth ) {
150
			if ( $this->fileMetadata[$key]['us_user'] != $this->userId ) {
151
				throw new UploadStashWrongOwnerException( "This file ($key) doesn't "
152
					. "belong to the current user." );
153
			}
154
		}
155
156
		return $this->files[$key];
157
	}
158
159
	/**
160
	 * Getter for file metadata.
161
	 *
162
	 * @param string $key Key under which file information is stored
163
	 * @return array
164
	 */
165
	public function getMetadata( $key ) {
166
		$this->getFile( $key );
167
168
		return $this->fileMetadata[$key];
169
	}
170
171
	/**
172
	 * Getter for fileProps
173
	 *
174
	 * @param string $key Key under which file information is stored
175
	 * @return array
176
	 */
177
	public function getFileProps( $key ) {
178
		$this->getFile( $key );
179
180
		return $this->fileProps[$key];
181
	}
182
183
	/**
184
	 * Stash a file in a temp directory and record that we did this in the
185
	 * database, along with other metadata.
186
	 *
187
	 * @param string $path Path to file you want stashed
188
	 * @param string $sourceType The type of upload that generated this file
189
	 *   (currently, I believe, 'file' or null)
190
	 * @throws UploadStashBadPathException
191
	 * @throws UploadStashFileException
192
	 * @throws UploadStashNotLoggedInException
193
	 * @return UploadStashFile|null File, or null on failure
194
	 */
195
	public function stashFile( $path, $sourceType = null ) {
196
		if ( !is_file( $path ) ) {
197
			wfDebug( __METHOD__ . " tried to stash file at '$path', but it doesn't exist\n" );
198
			throw new UploadStashBadPathException( "path doesn't exist" );
199
		}
200
		$fileProps = FSFile::getPropsFromPath( $path );
201
		wfDebug( __METHOD__ . " stashing file at '$path'\n" );
202
203
		// we will be initializing from some tmpnam files that don't have extensions.
204
		// most of MediaWiki assumes all uploaded files have good extensions. So, we fix this.
205
		$extension = self::getExtensionForPath( $path );
206
		if ( !preg_match( "/\\.\\Q$extension\\E$/", $path ) ) {
207
			$pathWithGoodExtension = "$path.$extension";
208
		} else {
209
			$pathWithGoodExtension = $path;
210
		}
211
212
		// If no key was supplied, make one.  a mysql insertid would be totally
213
		// reasonable here, except that for historical reasons, the key is this
214
		// random thing instead.  At least it's not guessable.
215
		// Some things that when combined will make a suitably unique key.
216
		// see: http://www.jwz.org/doc/mid.html
217
		list( $usec, $sec ) = explode( ' ', microtime() );
218
		$usec = substr( $usec, 2 );
219
		$key = Wikimedia\base_convert( $sec . $usec, 10, 36 ) . '.' .
220
			Wikimedia\base_convert( mt_rand(), 10, 36 ) . '.' .
221
			$this->userId . '.' .
222
			$extension;
223
224
		$this->fileProps[$key] = $fileProps;
225
226
		if ( !preg_match( self::KEY_FORMAT_REGEX, $key ) ) {
227
			throw new UploadStashBadPathException( "key '$key' is not in a proper format" );
228
		}
229
230
		wfDebug( __METHOD__ . " key for '$path': $key\n" );
231
232
		// if not already in a temporary area, put it there
233
		$storeStatus = $this->repo->storeTemp( basename( $pathWithGoodExtension ), $path );
234
235
		if ( !$storeStatus->isOK() ) {
236
			// It is a convention in MediaWiki to only return one error per API
237
			// exception, even if multiple errors are available. We use reset()
238
			// to pick the "first" thing that was wrong, preferring errors to
239
			// warnings. This is a bit lame, as we may have more info in the
240
			// $storeStatus and we're throwing it away, but to fix it means
241
			// redesigning API errors significantly.
242
			// $storeStatus->value just contains the virtual URL (if anything)
243
			// which is probably useless to the caller.
244
			$error = $storeStatus->getErrorsArray();
245
			$error = reset( $error );
246 View Code Duplication
			if ( !count( $error ) ) {
247
				$error = $storeStatus->getWarningsArray();
248
				$error = reset( $error );
249
				if ( !count( $error ) ) {
250
					$error = [ 'unknown', 'no error recorded' ];
251
				}
252
			}
253
			// At this point, $error should contain the single "most important"
254
			// error, plus any parameters.
255
			$errorMsg = array_shift( $error );
256
			throw new UploadStashFileException( "Error storing file in '$path': "
257
				. wfMessage( $errorMsg, $error )->text() );
258
		}
259
		$stashPath = $storeStatus->value;
260
261
		// fetch the current user ID
262
		if ( !$this->isLoggedIn ) {
263
			throw new UploadStashNotLoggedInException( __METHOD__
264
				. ' No user is logged in, files must belong to users' );
265
		}
266
267
		// insert the file metadata into the db.
268
		wfDebug( __METHOD__ . " inserting $stashPath under $key\n" );
269
		$dbw = $this->repo->getMasterDB();
270
271
		$serializedFileProps = serialize( $fileProps );
272
		if ( strlen( $serializedFileProps ) > self::MAX_US_PROPS_SIZE ) {
273
			// Database is going to truncate this and make the field invalid.
274
			// Prioritize important metadata over file handler metadata.
275
			// File handler should be prepared to regenerate invalid metadata if needed.
276
			$fileProps['metadata'] = false;
277
			$serializedFileProps = serialize( $fileProps );
278
		}
279
280
		$this->fileMetadata[$key] = [
281
			'us_id' => $dbw->nextSequenceValue( 'uploadstash_us_id_seq' ),
282
			'us_user' => $this->userId,
283
			'us_key' => $key,
284
			'us_orig_path' => $path,
285
			'us_path' => $stashPath, // virtual URL
286
			'us_props' => $dbw->encodeBlob( $serializedFileProps ),
287
			'us_size' => $fileProps['size'],
288
			'us_sha1' => $fileProps['sha1'],
289
			'us_mime' => $fileProps['mime'],
290
			'us_media_type' => $fileProps['media_type'],
291
			'us_image_width' => $fileProps['width'],
292
			'us_image_height' => $fileProps['height'],
293
			'us_image_bits' => $fileProps['bits'],
294
			'us_source_type' => $sourceType,
295
			'us_timestamp' => $dbw->timestamp(),
296
			'us_status' => 'finished'
297
		];
298
299
		$dbw->insert(
300
			'uploadstash',
301
			$this->fileMetadata[$key],
302
			__METHOD__
303
		);
304
305
		// store the insertid in the class variable so immediate retrieval
306
		// (possibly laggy) isn't necesary.
307
		$this->fileMetadata[$key]['us_id'] = $dbw->insertId();
308
309
		# create the UploadStashFile object for this file.
310
		$this->initFile( $key );
311
312
		return $this->getFile( $key );
313
	}
314
315
	/**
316
	 * Remove all files from the stash.
317
	 * Does not clean up files in the repo, just the record of them.
318
	 *
319
	 * @throws UploadStashNotLoggedInException
320
	 * @return bool Success
321
	 */
322
	public function clear() {
323
		if ( !$this->isLoggedIn ) {
324
			throw new UploadStashNotLoggedInException( __METHOD__
325
				. ' No user is logged in, files must belong to users' );
326
		}
327
328
		wfDebug( __METHOD__ . ' clearing all rows for user ' . $this->userId . "\n" );
329
		$dbw = $this->repo->getMasterDB();
330
		$dbw->delete(
331
			'uploadstash',
332
			[ 'us_user' => $this->userId ],
333
			__METHOD__
334
		);
335
336
		# destroy objects.
337
		$this->files = [];
338
		$this->fileMetadata = [];
339
340
		return true;
341
	}
342
343
	/**
344
	 * Remove a particular file from the stash.  Also removes it from the repo.
345
	 *
346
	 * @param string $key
347
	 * @throws UploadStashNoSuchKeyException|UploadStashNotLoggedInException
348
	 * @throws UploadStashWrongOwnerException
349
	 * @return bool Success
350
	 */
351
	public function removeFile( $key ) {
352
		if ( !$this->isLoggedIn ) {
353
			throw new UploadStashNotLoggedInException( __METHOD__
354
				. ' No user is logged in, files must belong to users' );
355
		}
356
357
		$dbw = $this->repo->getMasterDB();
358
359
		// this is a cheap query. it runs on the master so that this function
360
		// still works when there's lag. It won't be called all that often.
361
		$row = $dbw->selectRow(
362
			'uploadstash',
363
			'us_user',
364
			[ 'us_key' => $key ],
365
			__METHOD__
366
		);
367
368
		if ( !$row ) {
369
			throw new UploadStashNoSuchKeyException( "No such key ($key), cannot remove" );
370
		}
371
372
		if ( $row->us_user != $this->userId ) {
373
			throw new UploadStashWrongOwnerException( "Can't delete: "
374
				. "the file ($key) doesn't belong to this user." );
375
		}
376
377
		return $this->removeFileNoAuth( $key );
378
	}
379
380
	/**
381
	 * Remove a file (see removeFile), but doesn't check ownership first.
382
	 *
383
	 * @param string $key
384
	 * @return bool Success
385
	 */
386
	public function removeFileNoAuth( $key ) {
387
		wfDebug( __METHOD__ . " clearing row $key\n" );
388
389
		// Ensure we have the UploadStashFile loaded for this key
390
		$this->getFile( $key, true );
391
392
		$dbw = $this->repo->getMasterDB();
393
394
		$dbw->delete(
395
			'uploadstash',
396
			[ 'us_key' => $key ],
397
			__METHOD__
398
		);
399
400
		/** @todo Look into UnregisteredLocalFile and find out why the rv here is
401
		 *  sometimes wrong (false when file was removed). For now, ignore.
402
		 */
403
		$this->files[$key]->remove();
404
405
		unset( $this->files[$key] );
406
		unset( $this->fileMetadata[$key] );
407
408
		return true;
409
	}
410
411
	/**
412
	 * List all files in the stash.
413
	 *
414
	 * @throws UploadStashNotLoggedInException
415
	 * @return array
416
	 */
417
	public function listFiles() {
418
		if ( !$this->isLoggedIn ) {
419
			throw new UploadStashNotLoggedInException( __METHOD__
420
				. ' No user is logged in, files must belong to users' );
421
		}
422
423
		$dbr = $this->repo->getSlaveDB();
424
		$res = $dbr->select(
425
			'uploadstash',
426
			'us_key',
427
			[ 'us_user' => $this->userId ],
428
			__METHOD__
429
		);
430
431
		if ( !is_object( $res ) || $res->numRows() == 0 ) {
432
			// nothing to do.
433
			return false;
434
		}
435
436
		// finish the read before starting writes.
437
		$keys = [];
438
		foreach ( $res as $row ) {
439
			array_push( $keys, $row->us_key );
440
		}
441
442
		return $keys;
443
	}
444
445
	/**
446
	 * Find or guess extension -- ensuring that our extension matches our MIME type.
447
	 * Since these files are constructed from php tempnames they may not start off
448
	 * with an extension.
449
	 * XXX this is somewhat redundant with the checks that ApiUpload.php does with incoming
450
	 * uploads versus the desired filename. Maybe we can get that passed to us...
451
	 * @param string $path
452
	 * @throws UploadStashFileException
453
	 * @return string
454
	 */
455
	public static function getExtensionForPath( $path ) {
456
		global $wgFileBlacklist;
457
		// Does this have an extension?
458
		$n = strrpos( $path, '.' );
459
		$extension = null;
460
		if ( $n !== false ) {
461
			$extension = $n ? substr( $path, $n + 1 ) : '';
462
		} else {
463
			// If not, assume that it should be related to the MIME type of the original file.
464
			$magic = MimeMagic::singleton();
465
			$mimeType = $magic->guessMimeType( $path );
466
			$extensions = explode( ' ', MimeMagic::singleton()->getExtensionsForType( $mimeType ) );
467
			if ( count( $extensions ) ) {
468
				$extension = $extensions[0];
469
			}
470
		}
471
472
		if ( is_null( $extension ) ) {
473
			throw new UploadStashFileException( "extension is null" );
474
		}
475
476
		$extension = File::normalizeExtension( $extension );
477
		if ( in_array( $extension, $wgFileBlacklist ) ) {
478
			// The file should already be checked for being evil.
479
			// However, if somehow we got here, we definitely
480
			// don't want to give it an extension of .php and
481
			// put it in a web accesible directory.
482
			return '';
483
		}
484
485
		return $extension;
486
	}
487
488
	/**
489
	 * Helper function: do the actual database query to fetch file metadata.
490
	 *
491
	 * @param string $key
492
	 * @param int $readFromDB Constant (default: DB_SLAVE)
493
	 * @return bool
494
	 */
495
	protected function fetchFileMetadata( $key, $readFromDB = DB_SLAVE ) {
496
		// populate $fileMetadata[$key]
497
		$dbr = null;
0 ignored issues
show
Unused Code introduced by
$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 $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
498
		if ( $readFromDB === DB_MASTER ) {
499
			// sometimes reading from the master is necessary, if there's replication lag.
500
			$dbr = $this->repo->getMasterDB();
501
		} else {
502
			$dbr = $this->repo->getSlaveDB();
503
		}
504
505
		$row = $dbr->selectRow(
506
			'uploadstash',
507
			'*',
508
			[ 'us_key' => $key ],
509
			__METHOD__
510
		);
511
512
		if ( !is_object( $row ) ) {
513
			// key wasn't present in the database. this will happen sometimes.
514
			return false;
515
		}
516
517
		$this->fileMetadata[$key] = (array)$row;
518
		$this->fileMetadata[$key]['us_props'] = $dbr->decodeBlob( $row->us_props );
519
520
		return true;
521
	}
522
523
	/**
524
	 * Helper function: Initialize the UploadStashFile for a given file.
525
	 *
526
	 * @param string $key Key under which to store the object
527
	 * @throws UploadStashZeroLengthFileException
528
	 * @return bool
529
	 */
530
	protected function initFile( $key ) {
531
		$file = new UploadStashFile( $this->repo, $this->fileMetadata[$key]['us_path'], $key );
532
		if ( $file->getSize() === 0 ) {
533
			throw new UploadStashZeroLengthFileException( "File is zero length" );
534
		}
535
		$this->files[$key] = $file;
536
537
		return true;
538
	}
539
}
540
541
class UploadStashFile extends UnregisteredLocalFile {
542
	private $fileKey;
543
	private $urlName;
544
	protected $url;
545
546
	/**
547
	 * A LocalFile wrapper around a file that has been temporarily stashed,
548
	 * so we can do things like create thumbnails for it. Arguably
549
	 * UnregisteredLocalFile should be handling its own file repo but that
550
	 * class is a bit retarded currently.
551
	 *
552
	 * @param FileRepo $repo Repository where we should find the path
553
	 * @param string $path Path to file
554
	 * @param string $key Key to store the path and any stashed data under
555
	 * @throws UploadStashBadPathException
556
	 * @throws UploadStashFileNotFoundException
557
	 */
558
	public function __construct( $repo, $path, $key ) {
559
		$this->fileKey = $key;
560
561
		// resolve mwrepo:// urls
562
		if ( $repo->isVirtualUrl( $path ) ) {
563
			$path = $repo->resolveVirtualUrl( $path );
564
		} else {
565
			// check if path appears to be sane, no parent traversals,
566
			// and is in this repo's temp zone.
567
			$repoTempPath = $repo->getZonePath( 'temp' );
568
			if ( ( !$repo->validateFilename( $path ) ) ||
569
				( strpos( $path, $repoTempPath ) !== 0 )
570
			) {
571
				wfDebug( "UploadStash: tried to construct an UploadStashFile "
572
					. "from a file that should already exist at '$path', but path is not valid\n" );
573
				throw new UploadStashBadPathException( 'path is not valid' );
574
			}
575
576
			// check if path exists! and is a plain file.
577
			if ( !$repo->fileExists( $path ) ) {
578
				wfDebug( "UploadStash: tried to construct an UploadStashFile from "
579
					. "a file that should already exist at '$path', but path is not found\n" );
580
				throw new UploadStashFileNotFoundException( 'cannot find path, or not a plain file' );
581
			}
582
		}
583
584
		parent::__construct( false, $repo, $path, false );
585
586
		$this->name = basename( $this->path );
587
	}
588
589
	/**
590
	 * A method needed by the file transforming and scaling routines in File.php
591
	 * We do not necessarily care about doing the description at this point
592
	 * However, we also can't return the empty string, as the rest of MediaWiki
593
	 * demands this (and calls to imagemagick convert require it to be there)
594
	 *
595
	 * @return string Dummy value
596
	 */
597
	public function getDescriptionUrl() {
598
		return $this->getUrl();
599
	}
600
601
	/**
602
	 * Get the path for the thumbnail (actually any transformation of this file)
603
	 * The actual argument is the result of thumbName although we seem to have
604
	 * buggy code elsewhere that expects a boolean 'suffix'
605
	 *
606
	 * @param string $thumbName Name of thumbnail (e.g. "120px-123456.jpg" ),
607
	 *   or false to just get the path
608
	 * @return string Path thumbnail should take on filesystem, or containing
609
	 *   directory if thumbname is false
610
	 */
611
	public function getThumbPath( $thumbName = false ) {
612
		$path = dirname( $this->path );
613
		if ( $thumbName !== false ) {
614
			$path .= "/$thumbName";
615
		}
616
617
		return $path;
618
	}
619
620
	/**
621
	 * Return the file/url base name of a thumbnail with the specified parameters.
622
	 * We override this because we want to use the pretty url name instead of the
623
	 * ugly file name.
624
	 *
625
	 * @param array $params Handler-specific parameters
626
	 * @param int $flags Bitfield that supports THUMB_* constants
627
	 * @return string|null Base name for URL, like '120px-12345.jpg', or null if there is no handler
628
	 */
629
	function thumbName( $params, $flags = 0 ) {
630
		return $this->generateThumbName( $this->getUrlName(), $params );
631
	}
632
633
	/**
634
	 * Helper function -- given a 'subpage', return the local URL,
635
	 * e.g. /wiki/Special:UploadStash/subpage
636
	 * @param string $subPage
637
	 * @return string Local URL for this subpage in the Special:UploadStash space.
638
	 */
639
	private function getSpecialUrl( $subPage ) {
640
		return SpecialPage::getTitleFor( 'UploadStash', $subPage )->getLocalURL();
641
	}
642
643
	/**
644
	 * Get a URL to access the thumbnail
645
	 * This is required because the model of how files work requires that
646
	 * the thumbnail urls be predictable. However, in our model the URL is
647
	 * not based on the filename (that's hidden in the db)
648
	 *
649
	 * @param string $thumbName Basename of thumbnail file -- however, we don't
650
	 *   want to use the file exactly
651
	 * @return string URL to access thumbnail, or URL with partial path
652
	 */
653
	public function getThumbUrl( $thumbName = false ) {
654
		wfDebug( __METHOD__ . " getting for $thumbName \n" );
655
656
		return $this->getSpecialUrl( 'thumb/' . $this->getUrlName() . '/' . $thumbName );
657
	}
658
659
	/**
660
	 * The basename for the URL, which we want to not be related to the filename.
661
	 * Will also be used as the lookup key for a thumbnail file.
662
	 *
663
	 * @return string Base url name, like '120px-123456.jpg'
664
	 */
665
	public function getUrlName() {
666
		if ( !$this->urlName ) {
667
			$this->urlName = $this->fileKey;
668
		}
669
670
		return $this->urlName;
671
	}
672
673
	/**
674
	 * Return the URL of the file, if for some reason we wanted to download it
675
	 * We tend not to do this for the original file, but we do want thumb icons
676
	 *
677
	 * @return string Url
678
	 */
679
	public function getUrl() {
680
		if ( !isset( $this->url ) ) {
681
			$this->url = $this->getSpecialUrl( 'file/' . $this->getUrlName() );
682
		}
683
684
		return $this->url;
685
	}
686
687
	/**
688
	 * Parent classes use this method, for no obvious reason, to return the path
689
	 * (relative to wiki root, I assume). But with this class, the URL is
690
	 * unrelated to the path.
691
	 *
692
	 * @return string Url
693
	 */
694
	public function getFullUrl() {
695
		return $this->getUrl();
696
	}
697
698
	/**
699
	 * Getter for file key (the unique id by which this file's location &
700
	 * metadata is stored in the db)
701
	 *
702
	 * @return string File key
703
	 */
704
	public function getFileKey() {
705
		return $this->fileKey;
706
	}
707
708
	/**
709
	 * Remove the associated temporary file
710
	 * @return status Success
711
	 */
712
	public function remove() {
713
		if ( !$this->repo->fileExists( $this->path ) ) {
714
			// Maybe the file's already been removed? This could totally happen in UploadBase.
715
			return true;
716
		}
717
718
		return $this->repo->freeTemp( $this->path );
719
	}
720
721
	public function exists() {
722
		return $this->repo->fileExists( $this->path );
723
	}
724
}
725
726
class UploadStashException extends MWException {
727
}
728
729
class UploadStashFileNotFoundException extends UploadStashException {
730
}
731
732
class UploadStashBadPathException extends UploadStashException {
733
}
734
735
class UploadStashFileException extends UploadStashException {
736
}
737
738
class UploadStashZeroLengthFileException extends UploadStashException {
739
}
740
741
class UploadStashNotLoggedInException extends UploadStashException {
742
}
743
744
class UploadStashWrongOwnerException extends UploadStashException {
745
}
746
747
class UploadStashNoSuchKeyException extends UploadStashException {
748
}
749