Completed
Branch master (86dc85)
by
unknown
23:45
created

File::getUrlRel()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 3
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
/**
3
 * @defgroup FileAbstraction File abstraction
4
 * @ingroup FileRepo
5
 *
6
 * Represents files in a repository.
7
 */
8
9
/**
10
 * Base code for files.
11
 *
12
 * This program is free software; you can redistribute it and/or modify
13
 * it under the terms of the GNU General Public License as published by
14
 * the Free Software Foundation; either version 2 of the License, or
15
 * (at your option) any later version.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
 * GNU General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU General Public License along
23
 * with this program; if not, write to the Free Software Foundation, Inc.,
24
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25
 * http://www.gnu.org/copyleft/gpl.html
26
 *
27
 * @file
28
 * @ingroup FileAbstraction
29
 */
30
31
/**
32
 * Implements some public methods and some protected utility functions which
33
 * are required by multiple child classes. Contains stub functionality for
34
 * unimplemented public methods.
35
 *
36
 * Stub functions which should be overridden are marked with STUB. Some more
37
 * concrete functions are also typically overridden by child classes.
38
 *
39
 * Note that only the repo object knows what its file class is called. You should
40
 * never name a file class explictly outside of the repo class. Instead use the
41
 * repo's factory functions to generate file objects, for example:
42
 *
43
 * RepoGroup::singleton()->getLocalRepo()->newFile( $title );
44
 *
45
 * The convenience functions wfLocalFile() and wfFindFile() should be sufficient
46
 * in most cases.
47
 *
48
 * @ingroup FileAbstraction
49
 */
50
abstract class File implements IDBAccessObject {
51
	// Bitfield values akin to the Revision deletion constants
52
	const DELETED_FILE = 1;
53
	const DELETED_COMMENT = 2;
54
	const DELETED_USER = 4;
55
	const DELETED_RESTRICTED = 8;
56
57
	/** Force rendering in the current process */
58
	const RENDER_NOW = 1;
59
	/**
60
	 * Force rendering even if thumbnail already exist and using RENDER_NOW
61
	 * I.e. you have to pass both flags: File::RENDER_NOW | File::RENDER_FORCE
62
	 */
63
	const RENDER_FORCE = 2;
64
65
	const DELETE_SOURCE = 1;
66
67
	// Audience options for File::getDescription()
68
	const FOR_PUBLIC = 1;
69
	const FOR_THIS_USER = 2;
70
	const RAW = 3;
71
72
	// Options for File::thumbName()
73
	const THUMB_FULL_NAME = 1;
74
75
	/**
76
	 * Some member variables can be lazy-initialised using __get(). The
77
	 * initialisation function for these variables is always a function named
78
	 * like getVar(), where Var is the variable name with upper-case first
79
	 * letter.
80
	 *
81
	 * The following variables are initialised in this way in this base class:
82
	 *    name, extension, handler, path, canRender, isSafeFile,
83
	 *    transformScript, hashPath, pageCount, url
84
	 *
85
	 * Code within this class should generally use the accessor function
86
	 * directly, since __get() isn't re-entrant and therefore causes bugs that
87
	 * depend on initialisation order.
88
	 */
89
90
	/**
91
	 * The following member variables are not lazy-initialised
92
	 */
93
94
	/** @var FileRepo|LocalRepo|ForeignAPIRepo|bool */
95
	public $repo;
96
97
	/** @var Title|string|bool */
98
	protected $title;
99
100
	/** @var string Text of last error */
101
	protected $lastError;
102
103
	/** @var string Main part of the title, with underscores (Title::getDBkey) */
104
	protected $redirected;
105
106
	/** @var Title */
107
	protected $redirectedTitle;
108
109
	/** @var FSFile|bool False if undefined */
110
	protected $fsFile;
111
112
	/** @var MediaHandler */
113
	protected $handler;
114
115
	/** @var string The URL corresponding to one of the four basic zones */
116
	protected $url;
117
118
	/** @var string File extension */
119
	protected $extension;
120
121
	/** @var string The name of a file from its title object */
122
	protected $name;
123
124
	/** @var string The storage path corresponding to one of the zones */
125
	protected $path;
126
127
	/** @var string Relative path including trailing slash */
128
	protected $hashPath;
129
130
	/** @var string Number of pages of a multipage document, or false for
131
	 *    documents which aren't multipage documents
132
	 */
133
	protected $pageCount;
134
135
	/** @var string URL of transformscript (for example thumb.php) */
136
	protected $transformScript;
137
138
	/** @var Title */
139
	protected $redirectTitle;
140
141
	/** @var bool Whether the output of transform() for this file is likely to be valid. */
142
	protected $canRender;
143
144
	/** @var bool Whether this media file is in a format that is unlikely to
145
	 *    contain viruses or malicious content
146
	 */
147
	protected $isSafeFile;
148
149
	/** @var string Required Repository class type */
150
	protected $repoClass = 'FileRepo';
151
152
	/** @var array Cache of tmp filepaths pointing to generated bucket thumbnails, keyed by width */
153
	protected $tmpBucketedThumbCache = [];
154
155
	/**
156
	 * Call this constructor from child classes.
157
	 *
158
	 * Both $title and $repo are optional, though some functions
159
	 * may return false or throw exceptions if they are not set.
160
	 * Most subclasses will want to call assertRepoDefined() here.
161
	 *
162
	 * @param Title|string|bool $title
163
	 * @param FileRepo|bool $repo
164
	 */
165
	function __construct( $title, $repo ) {
166
		// Some subclasses do not use $title, but set name/title some other way
167
		if ( $title !== false ) {
168
			$title = self::normalizeTitle( $title, 'exception' );
169
		}
170
		$this->title = $title;
171
		$this->repo = $repo;
172
	}
173
174
	/**
175
	 * Given a string or Title object return either a
176
	 * valid Title object with namespace NS_FILE or null
177
	 *
178
	 * @param Title|string $title
179
	 * @param string|bool $exception Use 'exception' to throw an error on bad titles
180
	 * @throws MWException
181
	 * @return Title|null
182
	 */
183
	static function normalizeTitle( $title, $exception = false ) {
184
		$ret = $title;
185
		if ( $ret instanceof Title ) {
186
			# Normalize NS_MEDIA -> NS_FILE
187
			if ( $ret->getNamespace() == NS_MEDIA ) {
188
				$ret = Title::makeTitleSafe( NS_FILE, $ret->getDBkey() );
189
			# Sanity check the title namespace
190
			} elseif ( $ret->getNamespace() !== NS_FILE ) {
191
				$ret = null;
192
			}
193
		} else {
194
			# Convert strings to Title objects
195
			$ret = Title::makeTitleSafe( NS_FILE, (string)$ret );
196
		}
197
		if ( !$ret && $exception !== false ) {
198
			throw new MWException( "`$title` is not a valid file title." );
199
		}
200
201
		return $ret;
202
	}
203
204
	function __get( $name ) {
205
		$function = [ $this, 'get' . ucfirst( $name ) ];
206
		if ( !is_callable( $function ) ) {
207
			return null;
208
		} else {
209
			$this->$name = call_user_func( $function );
210
211
			return $this->$name;
212
		}
213
	}
214
215
	/**
216
	 * Normalize a file extension to the common form, making it lowercase and checking some synonyms,
217
	 * and ensure it's clean. Extensions with non-alphanumeric characters will be discarded.
218
	 * Keep in sync with mw.Title.normalizeExtension() in JS.
219
	 *
220
	 * @param string $extension File extension (without the leading dot)
221
	 * @return string File extension in canonical form
222
	 */
223
	static function normalizeExtension( $extension ) {
224
		$lower = strtolower( $extension );
225
		$squish = [
226
			'htm' => 'html',
227
			'jpeg' => 'jpg',
228
			'mpeg' => 'mpg',
229
			'tiff' => 'tif',
230
			'ogv' => 'ogg' ];
231
		if ( isset( $squish[$lower] ) ) {
232
			return $squish[$lower];
233
		} elseif ( preg_match( '/^[0-9a-z]+$/', $lower ) ) {
234
			return $lower;
235
		} else {
236
			return '';
237
		}
238
	}
239
240
	/**
241
	 * Checks if file extensions are compatible
242
	 *
243
	 * @param File $old Old file
244
	 * @param string $new New name
245
	 *
246
	 * @return bool|null
247
	 */
248
	static function checkExtensionCompatibility( File $old, $new ) {
249
		$oldMime = $old->getMimeType();
250
		$n = strrpos( $new, '.' );
251
		$newExt = self::normalizeExtension( $n ? substr( $new, $n + 1 ) : '' );
252
		$mimeMagic = MimeMagic::singleton();
253
254
		return $mimeMagic->isMatchingExtension( $newExt, $oldMime );
255
	}
256
257
	/**
258
	 * Upgrade the database row if there is one
259
	 * Called by ImagePage
260
	 * STUB
261
	 */
262
	function upgradeRow() {
263
	}
264
265
	/**
266
	 * Split an internet media type into its two components; if not
267
	 * a two-part name, set the minor type to 'unknown'.
268
	 *
269
	 * @param string $mime "text/html" etc
270
	 * @return array ("text", "html") etc
271
	 */
272
	public static function splitMime( $mime ) {
273
		if ( strpos( $mime, '/' ) !== false ) {
274
			return explode( '/', $mime, 2 );
275
		} else {
276
			return [ $mime, 'unknown' ];
277
		}
278
	}
279
280
	/**
281
	 * Callback for usort() to do file sorts by name
282
	 *
283
	 * @param File $a
284
	 * @param File $b
285
	 * @return int Result of name comparison
286
	 */
287
	public static function compare( File $a, File $b ) {
288
		return strcmp( $a->getName(), $b->getName() );
289
	}
290
291
	/**
292
	 * Return the name of this file
293
	 *
294
	 * @return string
295
	 */
296
	public function getName() {
297
		if ( !isset( $this->name ) ) {
298
			$this->assertRepoDefined();
299
			$this->name = $this->repo->getNameFromTitle( $this->title );
300
		}
301
302
		return $this->name;
303
	}
304
305
	/**
306
	 * Get the file extension, e.g. "svg"
307
	 *
308
	 * @return string
309
	 */
310
	function getExtension() {
311
		if ( !isset( $this->extension ) ) {
312
			$n = strrpos( $this->getName(), '.' );
313
			$this->extension = self::normalizeExtension(
314
				$n ? substr( $this->getName(), $n + 1 ) : '' );
315
		}
316
317
		return $this->extension;
318
	}
319
320
	/**
321
	 * Return the associated title object
322
	 *
323
	 * @return Title
324
	 */
325
	public function getTitle() {
326
		return $this->title;
327
	}
328
329
	/**
330
	 * Return the title used to find this file
331
	 *
332
	 * @return Title
333
	 */
334
	public function getOriginalTitle() {
335
		if ( $this->redirected ) {
336
			return $this->getRedirectedTitle();
337
		}
338
339
		return $this->title;
340
	}
341
342
	/**
343
	 * Return the URL of the file
344
	 *
345
	 * @return string
346
	 */
347
	public function getUrl() {
348
		if ( !isset( $this->url ) ) {
349
			$this->assertRepoDefined();
350
			$ext = $this->getExtension();
351
			$this->url = $this->repo->getZoneUrl( 'public', $ext ) . '/' . $this->getUrlRel();
352
		}
353
354
		return $this->url;
355
	}
356
357
	/*
358
	 * Get short description URL for a files based on the page ID
359
	 *
360
	 * @return string|null
361
	 * @since 1.27
362
	 */
363
	public function getDescriptionShortUrl() {
364
		return null;
365
	}
366
367
	/**
368
	 * Return a fully-qualified URL to the file.
369
	 * Upload URL paths _may or may not_ be fully qualified, so
370
	 * we check. Local paths are assumed to belong on $wgServer.
371
	 *
372
	 * @return string
373
	 */
374
	public function getFullUrl() {
375
		return wfExpandUrl( $this->getUrl(), PROTO_RELATIVE );
376
	}
377
378
	/**
379
	 * @return string
380
	 */
381
	public function getCanonicalUrl() {
382
		return wfExpandUrl( $this->getUrl(), PROTO_CANONICAL );
383
	}
384
385
	/**
386
	 * @return string
387
	 */
388
	function getViewURL() {
389
		if ( $this->mustRender() ) {
390
			if ( $this->canRender() ) {
391
				return $this->createThumb( $this->getWidth() );
392
			} else {
393
				wfDebug( __METHOD__ . ': supposed to render ' . $this->getName() .
394
					' (' . $this->getMimeType() . "), but can't!\n" );
395
396
				return $this->getUrl(); # hm... return NULL?
397
			}
398
		} else {
399
			return $this->getUrl();
400
		}
401
	}
402
403
	/**
404
	 * Return the storage path to the file. Note that this does
405
	 * not mean that a file actually exists under that location.
406
	 *
407
	 * This path depends on whether directory hashing is active or not,
408
	 * i.e. whether the files are all found in the same directory,
409
	 * or in hashed paths like /images/3/3c.
410
	 *
411
	 * Most callers don't check the return value, but ForeignAPIFile::getPath
412
	 * returns false.
413
	 *
414
	 * @return string|bool ForeignAPIFile::getPath can return false
415
	 */
416
	public function getPath() {
417
		if ( !isset( $this->path ) ) {
418
			$this->assertRepoDefined();
419
			$this->path = $this->repo->getZonePath( 'public' ) . '/' . $this->getRel();
420
		}
421
422
		return $this->path;
423
	}
424
425
	/**
426
	 * Get an FS copy or original of this file and return the path.
427
	 * Returns false on failure. Callers must not alter the file.
428
	 * Temporary files are cleared automatically.
429
	 *
430
	 * @return string|bool False on failure
431
	 */
432
	public function getLocalRefPath() {
433
		$this->assertRepoDefined();
434
		if ( !isset( $this->fsFile ) ) {
435
			$starttime = microtime( true );
436
			$this->fsFile = $this->repo->getLocalReference( $this->getPath() );
437
438
			$statTiming = microtime( true ) - $starttime;
439
			RequestContext::getMain()->getStats()->timing(
440
				'media.thumbnail.generate.fetchoriginal', 1000 * $statTiming );
441
442
			if ( !$this->fsFile ) {
443
				$this->fsFile = false; // null => false; cache negative hits
444
			}
445
		}
446
447
		return ( $this->fsFile )
448
			? $this->fsFile->getPath()
449
			: false;
450
	}
451
452
	/**
453
	 * Return the width of the image. Returns false if the width is unknown
454
	 * or undefined.
455
	 *
456
	 * STUB
457
	 * Overridden by LocalFile, UnregisteredLocalFile
458
	 *
459
	 * @param int $page
460
	 * @return int|bool
461
	 */
462
	public function getWidth( $page = 1 ) {
463
		return false;
464
	}
465
466
	/**
467
	 * Return the height of the image. Returns false if the height is unknown
468
	 * or undefined
469
	 *
470
	 * STUB
471
	 * Overridden by LocalFile, UnregisteredLocalFile
472
	 *
473
	 * @param int $page
474
	 * @return bool|int False on failure
475
	 */
476
	public function getHeight( $page = 1 ) {
477
		return false;
478
	}
479
480
	/**
481
	 * Return the smallest bucket from $wgThumbnailBuckets which is at least
482
	 * $wgThumbnailMinimumBucketDistance larger than $desiredWidth. The returned bucket, if any,
483
	 * will always be bigger than $desiredWidth.
484
	 *
485
	 * @param int $desiredWidth
486
	 * @param int $page
487
	 * @return bool|int
488
	 */
489
	public function getThumbnailBucket( $desiredWidth, $page = 1 ) {
490
		global $wgThumbnailBuckets, $wgThumbnailMinimumBucketDistance;
491
492
		$imageWidth = $this->getWidth( $page );
493
494
		if ( $imageWidth === false ) {
495
			return false;
496
		}
497
498
		if ( $desiredWidth > $imageWidth ) {
499
			return false;
500
		}
501
502
		if ( !$wgThumbnailBuckets ) {
503
			return false;
504
		}
505
506
		$sortedBuckets = $wgThumbnailBuckets;
507
508
		sort( $sortedBuckets );
509
510
		foreach ( $sortedBuckets as $bucket ) {
511
			if ( $bucket >= $imageWidth ) {
512
				return false;
513
			}
514
515
			if ( $bucket - $wgThumbnailMinimumBucketDistance > $desiredWidth ) {
516
				return $bucket;
517
			}
518
		}
519
520
		// Image is bigger than any available bucket
521
		return false;
522
	}
523
524
	/**
525
	 * Returns ID or name of user who uploaded the file
526
	 * STUB
527
	 *
528
	 * @param string $type 'text' or 'id'
529
	 * @return string|int
530
	 */
531
	public function getUser( $type = 'text' ) {
532
		return null;
533
	}
534
535
	/**
536
	 * Get the duration of a media file in seconds
537
	 *
538
	 * @return int
539
	 */
540
	public function getLength() {
541
		$handler = $this->getHandler();
542
		if ( $handler ) {
543
			return $handler->getLength( $this );
544
		} else {
545
			return 0;
546
		}
547
	}
548
549
	/**
550
	 * Return true if the file is vectorized
551
	 *
552
	 * @return bool
553
	 */
554
	public function isVectorized() {
555
		$handler = $this->getHandler();
556
		if ( $handler ) {
557
			return $handler->isVectorized( $this );
558
		} else {
559
			return false;
560
		}
561
	}
562
563
	/**
564
	 * Gives a (possibly empty) list of languages to render
565
	 * the file in.
566
	 *
567
	 * If the file doesn't have translations, or if the file
568
	 * format does not support that sort of thing, returns
569
	 * an empty array.
570
	 *
571
	 * @return array
572
	 * @since 1.23
573
	 */
574
	public function getAvailableLanguages() {
575
		$handler = $this->getHandler();
576
		if ( $handler ) {
577
			return $handler->getAvailableLanguages( $this );
578
		} else {
579
			return [];
580
		}
581
	}
582
583
	/**
584
	 * In files that support multiple language, what is the default language
585
	 * to use if none specified.
586
	 *
587
	 * @return string|null Lang code, or null if filetype doesn't support multiple languages.
588
	 * @since 1.23
589
	 */
590
	public function getDefaultRenderLanguage() {
591
		$handler = $this->getHandler();
592
		if ( $handler ) {
593
			return $handler->getDefaultRenderLanguage( $this );
594
		} else {
595
			return null;
596
		}
597
	}
598
599
	/**
600
	 * Will the thumbnail be animated if one would expect it to be.
601
	 *
602
	 * Currently used to add a warning to the image description page
603
	 *
604
	 * @return bool False if the main image is both animated
605
	 *   and the thumbnail is not. In all other cases must return
606
	 *   true. If image is not renderable whatsoever, should
607
	 *   return true.
608
	 */
609
	public function canAnimateThumbIfAppropriate() {
610
		$handler = $this->getHandler();
611
		if ( !$handler ) {
612
			// We cannot handle image whatsoever, thus
613
			// one would not expect it to be animated
614
			// so true.
615
			return true;
616
		} else {
617
			if ( $this->allowInlineDisplay()
618
				&& $handler->isAnimatedImage( $this )
619
				&& !$handler->canAnimateThumbnail( $this )
620
			) {
621
				// Image is animated, but thumbnail isn't.
622
				// This is unexpected to the user.
623
				return false;
624
			} else {
625
				// Image is not animated, so one would
626
				// not expect thumb to be
627
				return true;
628
			}
629
		}
630
	}
631
632
	/**
633
	 * Get handler-specific metadata
634
	 * Overridden by LocalFile, UnregisteredLocalFile
635
	 * STUB
636
	 * @return bool|array
637
	 */
638
	public function getMetadata() {
639
		return false;
640
	}
641
642
	/**
643
	 * Like getMetadata but returns a handler independent array of common values.
644
	 * @see MediaHandler::getCommonMetaArray()
645
	 * @return array|bool Array or false if not supported
646
	 * @since 1.23
647
	 */
648
	public function getCommonMetaArray() {
649
		$handler = $this->getHandler();
650
651
		if ( !$handler ) {
652
			return false;
653
		}
654
655
		return $handler->getCommonMetaArray( $this );
656
	}
657
658
	/**
659
	 * get versioned metadata
660
	 *
661
	 * @param array|string $metadata Array or string of (serialized) metadata
662
	 * @param int $version Version number.
663
	 * @return array Array containing metadata, or what was passed to it on fail
664
	 *   (unserializing if not array)
665
	 */
666
	public function convertMetadataVersion( $metadata, $version ) {
667
		$handler = $this->getHandler();
668
		if ( !is_array( $metadata ) ) {
669
			// Just to make the return type consistent
670
			$metadata = unserialize( $metadata );
671
		}
672
		if ( $handler ) {
673
			return $handler->convertMetadataVersion( $metadata, $version );
674
		} else {
675
			return $metadata;
676
		}
677
	}
678
679
	/**
680
	 * Return the bit depth of the file
681
	 * Overridden by LocalFile
682
	 * STUB
683
	 * @return int
684
	 */
685
	public function getBitDepth() {
686
		return 0;
687
	}
688
689
	/**
690
	 * Return the size of the image file, in bytes
691
	 * Overridden by LocalFile, UnregisteredLocalFile
692
	 * STUB
693
	 * @return bool
694
	 */
695
	public function getSize() {
696
		return false;
697
	}
698
699
	/**
700
	 * Returns the MIME type of the file.
701
	 * Overridden by LocalFile, UnregisteredLocalFile
702
	 * STUB
703
	 *
704
	 * @return string
705
	 */
706
	function getMimeType() {
707
		return 'unknown/unknown';
708
	}
709
710
	/**
711
	 * Return the type of the media in the file.
712
	 * Use the value returned by this function with the MEDIATYPE_xxx constants.
713
	 * Overridden by LocalFile,
714
	 * STUB
715
	 * @return string
716
	 */
717
	function getMediaType() {
718
		return MEDIATYPE_UNKNOWN;
719
	}
720
721
	/**
722
	 * Checks if the output of transform() for this file is likely
723
	 * to be valid. If this is false, various user elements will
724
	 * display a placeholder instead.
725
	 *
726
	 * Currently, this checks if the file is an image format
727
	 * that can be converted to a format
728
	 * supported by all browsers (namely GIF, PNG and JPEG),
729
	 * or if it is an SVG image and SVG conversion is enabled.
730
	 *
731
	 * @return bool
732
	 */
733
	function canRender() {
734
		if ( !isset( $this->canRender ) ) {
735
			$this->canRender = $this->getHandler() && $this->handler->canRender( $this ) && $this->exists();
736
		}
737
738
		return $this->canRender;
739
	}
740
741
	/**
742
	 * Accessor for __get()
743
	 * @return bool
744
	 */
745
	protected function getCanRender() {
746
		return $this->canRender();
747
	}
748
749
	/**
750
	 * Return true if the file is of a type that can't be directly
751
	 * rendered by typical browsers and needs to be re-rasterized.
752
	 *
753
	 * This returns true for everything but the bitmap types
754
	 * supported by all browsers, i.e. JPEG; GIF and PNG. It will
755
	 * also return true for any non-image formats.
756
	 *
757
	 * @return bool
758
	 */
759
	function mustRender() {
760
		return $this->getHandler() && $this->handler->mustRender( $this );
761
	}
762
763
	/**
764
	 * Alias for canRender()
765
	 *
766
	 * @return bool
767
	 */
768
	function allowInlineDisplay() {
769
		return $this->canRender();
770
	}
771
772
	/**
773
	 * Determines if this media file is in a format that is unlikely to
774
	 * contain viruses or malicious content. It uses the global
775
	 * $wgTrustedMediaFormats list to determine if the file is safe.
776
	 *
777
	 * This is used to show a warning on the description page of non-safe files.
778
	 * It may also be used to disallow direct [[media:...]] links to such files.
779
	 *
780
	 * Note that this function will always return true if allowInlineDisplay()
781
	 * or isTrustedFile() is true for this file.
782
	 *
783
	 * @return bool
784
	 */
785
	function isSafeFile() {
786
		if ( !isset( $this->isSafeFile ) ) {
787
			$this->isSafeFile = $this->getIsSafeFileUncached();
788
		}
789
790
		return $this->isSafeFile;
791
	}
792
793
	/**
794
	 * Accessor for __get()
795
	 *
796
	 * @return bool
797
	 */
798
	protected function getIsSafeFile() {
799
		return $this->isSafeFile();
800
	}
801
802
	/**
803
	 * Uncached accessor
804
	 *
805
	 * @return bool
806
	 */
807
	protected function getIsSafeFileUncached() {
808
		global $wgTrustedMediaFormats;
809
810
		if ( $this->allowInlineDisplay() ) {
811
			return true;
812
		}
813
		if ( $this->isTrustedFile() ) {
814
			return true;
815
		}
816
817
		$type = $this->getMediaType();
818
		$mime = $this->getMimeType();
819
		# wfDebug( "LocalFile::isSafeFile: type= $type, mime= $mime\n" );
820
821
		if ( !$type || $type === MEDIATYPE_UNKNOWN ) {
822
			return false; # unknown type, not trusted
823
		}
824
		if ( in_array( $type, $wgTrustedMediaFormats ) ) {
825
			return true;
826
		}
827
828
		if ( $mime === "unknown/unknown" ) {
829
			return false; # unknown type, not trusted
830
		}
831
		if ( in_array( $mime, $wgTrustedMediaFormats ) ) {
832
			return true;
833
		}
834
835
		return false;
836
	}
837
838
	/**
839
	 * Returns true if the file is flagged as trusted. Files flagged that way
840
	 * can be linked to directly, even if that is not allowed for this type of
841
	 * file normally.
842
	 *
843
	 * This is a dummy function right now and always returns false. It could be
844
	 * implemented to extract a flag from the database. The trusted flag could be
845
	 * set on upload, if the user has sufficient privileges, to bypass script-
846
	 * and html-filters. It may even be coupled with cryptographics signatures
847
	 * or such.
848
	 *
849
	 * @return bool
850
	 */
851
	function isTrustedFile() {
852
		# this could be implemented to check a flag in the database,
853
		# look for signatures, etc
854
		return false;
855
	}
856
857
	/**
858
	 * Load any lazy-loaded file object fields from source
859
	 *
860
	 * This is only useful when setting $flags
861
	 *
862
	 * Overridden by LocalFile to actually query the DB
863
	 *
864
	 * @param integer $flags Bitfield of File::READ_* constants
865
	 */
866
	public function load( $flags = 0 ) {
867
	}
868
869
	/**
870
	 * Returns true if file exists in the repository.
871
	 *
872
	 * Overridden by LocalFile to avoid unnecessary stat calls.
873
	 *
874
	 * @return bool Whether file exists in the repository.
875
	 */
876
	public function exists() {
877
		return $this->getPath() && $this->repo->fileExists( $this->path );
878
	}
879
880
	/**
881
	 * Returns true if file exists in the repository and can be included in a page.
882
	 * It would be unsafe to include private images, making public thumbnails inadvertently
883
	 *
884
	 * @return bool Whether file exists in the repository and is includable.
885
	 */
886
	public function isVisible() {
887
		return $this->exists();
888
	}
889
890
	/**
891
	 * @return string
892
	 */
893
	function getTransformScript() {
894
		if ( !isset( $this->transformScript ) ) {
895
			$this->transformScript = false;
0 ignored issues
show
Documentation Bug introduced by
The property $transformScript was declared of type string, but false is of type false. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
896
			if ( $this->repo ) {
897
				$script = $this->repo->getThumbScriptUrl();
898
				if ( $script ) {
899
					$this->transformScript = wfAppendQuery( $script, [ 'f' => $this->getName() ] );
900
				}
901
			}
902
		}
903
904
		return $this->transformScript;
905
	}
906
907
	/**
908
	 * Get a ThumbnailImage which is the same size as the source
909
	 *
910
	 * @param array $handlerParams
911
	 *
912
	 * @return string
913
	 */
914
	function getUnscaledThumb( $handlerParams = [] ) {
915
		$hp =& $handlerParams;
916
		$page = isset( $hp['page'] ) ? $hp['page'] : false;
917
		$width = $this->getWidth( $page );
918
		if ( !$width ) {
919
			return $this->iconThumb();
920
		}
921
		$hp['width'] = $width;
922
		// be sure to ignore any height specification as well (bug 62258)
923
		unset( $hp['height'] );
924
925
		return $this->transform( $hp );
926
	}
927
928
	/**
929
	 * Return the file name of a thumbnail with the specified parameters.
930
	 * Use File::THUMB_FULL_NAME to always get a name like "<params>-<source>".
931
	 * Otherwise, the format may be "<params>-<source>" or "<params>-thumbnail.<ext>".
932
	 *
933
	 * @param array $params Handler-specific parameters
934
	 * @param int $flags Bitfield that supports THUMB_* constants
935
	 * @return string|null
936
	 */
937
	public function thumbName( $params, $flags = 0 ) {
938
		$name = ( $this->repo && !( $flags & self::THUMB_FULL_NAME ) )
939
			? $this->repo->nameForThumb( $this->getName() )
940
			: $this->getName();
941
942
		return $this->generateThumbName( $name, $params );
943
	}
944
945
	/**
946
	 * Generate a thumbnail file name from a name and specified parameters
947
	 *
948
	 * @param string $name
949
	 * @param array $params Parameters which will be passed to MediaHandler::makeParamString
950
	 * @return string|null
951
	 */
952
	public function generateThumbName( $name, $params ) {
953
		if ( !$this->getHandler() ) {
954
			return null;
955
		}
956
		$extension = $this->getExtension();
957
		list( $thumbExt, ) = $this->getHandler()->getThumbType(
958
			$extension, $this->getMimeType(), $params );
959
		$thumbName = $this->getHandler()->makeParamString( $params );
960
961
		if ( $this->repo->supportsSha1URLs() ) {
962
			$thumbName .= '-' . $this->getSha1() . '.' . $thumbExt;
963
		} else {
964
			$thumbName .= '-' . $name;
965
966
			if ( $thumbExt != $extension ) {
967
				$thumbName .= ".$thumbExt";
968
			}
969
		}
970
971
		return $thumbName;
972
	}
973
974
	/**
975
	 * Create a thumbnail of the image having the specified width/height.
976
	 * The thumbnail will not be created if the width is larger than the
977
	 * image's width. Let the browser do the scaling in this case.
978
	 * The thumbnail is stored on disk and is only computed if the thumbnail
979
	 * file does not exist OR if it is older than the image.
980
	 * Returns the URL.
981
	 *
982
	 * Keeps aspect ratio of original image. If both width and height are
983
	 * specified, the generated image will be no bigger than width x height,
984
	 * and will also have correct aspect ratio.
985
	 *
986
	 * @param int $width Maximum width of the generated thumbnail
987
	 * @param int $height Maximum height of the image (optional)
988
	 *
989
	 * @return string
990
	 */
991
	public function createThumb( $width, $height = -1 ) {
992
		$params = [ 'width' => $width ];
993
		if ( $height != -1 ) {
994
			$params['height'] = $height;
995
		}
996
		$thumb = $this->transform( $params );
997
		if ( !$thumb || $thumb->isError() ) {
998
			return '';
999
		}
1000
1001
		return $thumb->getUrl();
1002
	}
1003
1004
	/**
1005
	 * Return either a MediaTransformError or placeholder thumbnail (if $wgIgnoreImageErrors)
1006
	 *
1007
	 * @param string $thumbPath Thumbnail storage path
1008
	 * @param string $thumbUrl Thumbnail URL
1009
	 * @param array $params
1010
	 * @param int $flags
1011
	 * @return MediaTransformOutput
1012
	 */
1013
	protected function transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags ) {
1014
		global $wgIgnoreImageErrors;
1015
1016
		$handler = $this->getHandler();
1017
		if ( $handler && $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
1018
			return $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
1019
		} else {
1020
			return new MediaTransformError( 'thumbnail_error',
1021
				$params['width'], 0, wfMessage( 'thumbnail-dest-create' )->text() );
1022
		}
1023
	}
1024
1025
	/**
1026
	 * Transform a media file
1027
	 *
1028
	 * @param array $params An associative array of handler-specific parameters.
1029
	 *   Typical keys are width, height and page.
1030
	 * @param int $flags A bitfield, may contain self::RENDER_NOW to force rendering
1031
	 * @return MediaTransformOutput|bool False on failure
1032
	 */
1033
	function transform( $params, $flags = 0 ) {
1034
		global $wgThumbnailEpoch;
1035
1036
		do {
1037
			if ( !$this->canRender() ) {
1038
				$thumb = $this->iconThumb();
1039
				break; // not a bitmap or renderable image, don't try
1040
			}
1041
1042
			// Get the descriptionUrl to embed it as comment into the thumbnail. Bug 19791.
1043
			$descriptionUrl = $this->getDescriptionUrl();
1044
			if ( $descriptionUrl ) {
1045
				$params['descriptionUrl'] = wfExpandUrl( $descriptionUrl, PROTO_CANONICAL );
1046
			}
1047
1048
			$handler = $this->getHandler();
1049
			$script = $this->getTransformScript();
1050
			if ( $script && !( $flags & self::RENDER_NOW ) ) {
1051
				// Use a script to transform on client request, if possible
1052
				$thumb = $handler->getScriptedTransform( $this, $script, $params );
1053
				if ( $thumb ) {
1054
					break;
1055
				}
1056
			}
1057
1058
			$normalisedParams = $params;
1059
			$handler->normaliseParams( $this, $normalisedParams );
1060
1061
			$thumbName = $this->thumbName( $normalisedParams );
1062
			$thumbUrl = $this->getThumbUrl( $thumbName );
1063
			$thumbPath = $this->getThumbPath( $thumbName ); // final thumb path
1064
1065
			if ( $this->repo ) {
1066
				// Defer rendering if a 404 handler is set up...
1067
				if ( $this->repo->canTransformVia404() && !( $flags & self::RENDER_NOW ) ) {
1068
					wfDebug( __METHOD__ . " transformation deferred.\n" );
1069
					// XXX: Pass in the storage path even though we are not rendering anything
1070
					// and the path is supposed to be an FS path. This is due to getScalerType()
1071
					// getting called on the path and clobbering $thumb->getUrl() if it's false.
1072
					$thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
1073
					break;
1074
				}
1075
				// Check if an up-to-date thumbnail already exists...
1076
				wfDebug( __METHOD__ . ": Doing stat for $thumbPath\n" );
1077
				if ( !( $flags & self::RENDER_FORCE ) && $this->repo->fileExists( $thumbPath ) ) {
1078
					$timestamp = $this->repo->getFileTimestamp( $thumbPath );
1079
					if ( $timestamp !== false && $timestamp >= $wgThumbnailEpoch ) {
1080
						// XXX: Pass in the storage path even though we are not rendering anything
1081
						// and the path is supposed to be an FS path. This is due to getScalerType()
1082
						// getting called on the path and clobbering $thumb->getUrl() if it's false.
1083
						$thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
1084
						$thumb->setStoragePath( $thumbPath );
1085
						break;
1086
					}
1087
				} elseif ( $flags & self::RENDER_FORCE ) {
1088
					wfDebug( __METHOD__ . " forcing rendering per flag File::RENDER_FORCE\n" );
1089
				}
1090
1091
				// If the backend is ready-only, don't keep generating thumbnails
1092
				// only to return transformation errors, just return the error now.
1093
				if ( $this->repo->getReadOnlyReason() !== false ) {
1094
					$thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
1095
					break;
1096
				}
1097
			}
1098
1099
			$tmpFile = $this->makeTransformTmpFile( $thumbPath );
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $tmpFile is correct as $this->makeTransformTmpFile($thumbPath) (which targets File::makeTransformTmpFile()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1100
1101
			if ( !$tmpFile ) {
1102
				$thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
1103
			} else {
1104
				$thumb = $this->generateAndSaveThumb( $tmpFile, $params, $flags );
1105
			}
1106
		} while ( false );
1107
1108
		return is_object( $thumb ) ? $thumb : false;
1109
	}
1110
1111
	/**
1112
	 * Generates a thumbnail according to the given parameters and saves it to storage
1113
	 * @param TempFSFile $tmpFile Temporary file where the rendered thumbnail will be saved
1114
	 * @param array $transformParams
1115
	 * @param int $flags
1116
	 * @return bool|MediaTransformOutput
1117
	 */
1118
	public function generateAndSaveThumb( $tmpFile, $transformParams, $flags ) {
1119
		global $wgIgnoreImageErrors;
1120
1121
		$stats = RequestContext::getMain()->getStats();
1122
1123
		$handler = $this->getHandler();
1124
1125
		$normalisedParams = $transformParams;
1126
		$handler->normaliseParams( $this, $normalisedParams );
1127
1128
		$thumbName = $this->thumbName( $normalisedParams );
1129
		$thumbUrl = $this->getThumbUrl( $thumbName );
1130
		$thumbPath = $this->getThumbPath( $thumbName ); // final thumb path
1131
1132
		$tmpThumbPath = $tmpFile->getPath();
1133
1134
		if ( $handler->supportsBucketing() ) {
1135
			$this->generateBucketsIfNeeded( $normalisedParams, $flags );
1136
		}
1137
1138
		$starttime = microtime( true );
1139
1140
		// Actually render the thumbnail...
1141
		$thumb = $handler->doTransform( $this, $tmpThumbPath, $thumbUrl, $transformParams );
1142
		$tmpFile->bind( $thumb ); // keep alive with $thumb
1143
1144
		$statTiming = microtime( true ) - $starttime;
1145
		$stats->timing( 'media.thumbnail.generate.transform', 1000 * $statTiming );
1146
1147
		if ( !$thumb ) { // bad params?
1148
			$thumb = false;
1149
		} elseif ( $thumb->isError() ) { // transform error
1150
			/** @var $thumb MediaTransformError */
1151
			$this->lastError = $thumb->toText();
1152
			// Ignore errors if requested
1153
			if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
1154
				$thumb = $handler->getTransform( $this, $tmpThumbPath, $thumbUrl, $transformParams );
1155
			}
1156
		} elseif ( $this->repo && $thumb->hasFile() && !$thumb->fileIsSource() ) {
1157
			// Copy the thumbnail from the file system into storage...
1158
1159
			$starttime = microtime( true );
1160
1161
			$disposition = $this->getThumbDisposition( $thumbName );
1162
			$status = $this->repo->quickImport( $tmpThumbPath, $thumbPath, $disposition );
1163
			if ( $status->isOK() ) {
1164
				$thumb->setStoragePath( $thumbPath );
1165
			} else {
1166
				$thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $transformParams, $flags );
1167
			}
1168
1169
			$statTiming = microtime( true ) - $starttime;
1170
			$stats->timing( 'media.thumbnail.generate.store', 1000 * $statTiming );
1171
1172
			// Give extensions a chance to do something with this thumbnail...
1173
			Hooks::run( 'FileTransformed', [ $this, $thumb, $tmpThumbPath, $thumbPath ] );
1174
		}
1175
1176
		return $thumb;
1177
	}
1178
1179
	/**
1180
	 * Generates chained bucketed thumbnails if needed
1181
	 * @param array $params
1182
	 * @param int $flags
1183
	 * @return bool Whether at least one bucket was generated
1184
	 */
1185
	protected function generateBucketsIfNeeded( $params, $flags = 0 ) {
1186
		if ( !$this->repo
1187
			|| !isset( $params['physicalWidth'] )
1188
			|| !isset( $params['physicalHeight'] )
1189
		) {
1190
			return false;
1191
		}
1192
1193
		$bucket = $this->getThumbnailBucket( $params['physicalWidth'] );
1194
1195
		if ( !$bucket || $bucket == $params['physicalWidth'] ) {
1196
			return false;
1197
		}
1198
1199
		$bucketPath = $this->getBucketThumbPath( $bucket );
1200
1201
		if ( $this->repo->fileExists( $bucketPath ) ) {
1202
			return false;
1203
		}
1204
1205
		$starttime = microtime( true );
1206
1207
		$params['physicalWidth'] = $bucket;
1208
		$params['width'] = $bucket;
1209
1210
		$params = $this->getHandler()->sanitizeParamsForBucketing( $params );
1211
1212
		$tmpFile = $this->makeTransformTmpFile( $bucketPath );
1213
1214
		if ( !$tmpFile ) {
1215
			return false;
1216
		}
1217
1218
		$thumb = $this->generateAndSaveThumb( $tmpFile, $params, $flags );
1219
1220
		$buckettime = microtime( true ) - $starttime;
1221
1222
		if ( !$thumb || $thumb->isError() ) {
1223
			return false;
1224
		}
1225
1226
		$this->tmpBucketedThumbCache[$bucket] = $tmpFile->getPath();
1227
		// For the caching to work, we need to make the tmp file survive as long as
1228
		// this object exists
1229
		$tmpFile->bind( $this );
1230
1231
		RequestContext::getMain()->getStats()->timing(
1232
			'media.thumbnail.generate.bucket', 1000 * $buckettime );
1233
1234
		return true;
1235
	}
1236
1237
	/**
1238
	 * Returns the most appropriate source image for the thumbnail, given a target thumbnail size
1239
	 * @param array $params
1240
	 * @return array Source path and width/height of the source
1241
	 */
1242
	public function getThumbnailSource( $params ) {
1243
		if ( $this->repo
1244
			&& $this->getHandler()->supportsBucketing()
1245
			&& isset( $params['physicalWidth'] )
1246
			&& $bucket = $this->getThumbnailBucket( $params['physicalWidth'] )
1247
		) {
1248
			if ( $this->getWidth() != 0 ) {
1249
				$bucketHeight = round( $this->getHeight() * ( $bucket / $this->getWidth() ) );
1250
			} else {
1251
				$bucketHeight = 0;
1252
			}
1253
1254
			// Try to avoid reading from storage if the file was generated by this script
1255
			if ( isset( $this->tmpBucketedThumbCache[$bucket] ) ) {
1256
				$tmpPath = $this->tmpBucketedThumbCache[$bucket];
1257
1258
				if ( file_exists( $tmpPath ) ) {
1259
					return [
1260
						'path' => $tmpPath,
1261
						'width' => $bucket,
1262
						'height' => $bucketHeight
1263
					];
1264
				}
1265
			}
1266
1267
			$bucketPath = $this->getBucketThumbPath( $bucket );
1268
1269
			if ( $this->repo->fileExists( $bucketPath ) ) {
1270
				$fsFile = $this->repo->getLocalReference( $bucketPath );
1271
1272
				if ( $fsFile ) {
1273
					return [
1274
						'path' => $fsFile->getPath(),
1275
						'width' => $bucket,
1276
						'height' => $bucketHeight
1277
					];
1278
				}
1279
			}
1280
		}
1281
1282
		// Thumbnailing a very large file could result in network saturation if
1283
		// everyone does it at once.
1284
		if ( $this->getSize() >= 1e7 ) { // 10MB
1285
			$that = $this;
1286
			$work = new PoolCounterWorkViaCallback( 'GetLocalFileCopy', sha1( $this->getName() ),
1287
				[
1288
					'doWork' => function () use ( $that ) {
1289
						return $that->getLocalRefPath();
1290
					}
1291
				]
1292
			);
1293
			$srcPath = $work->execute();
1294
		} else {
1295
			$srcPath = $this->getLocalRefPath();
1296
		}
1297
1298
		// Original file
1299
		return [
1300
			'path' => $srcPath,
1301
			'width' => $this->getWidth(),
1302
			'height' => $this->getHeight()
1303
		];
1304
	}
1305
1306
	/**
1307
	 * Returns the repo path of the thumb for a given bucket
1308
	 * @param int $bucket
1309
	 * @return string
1310
	 */
1311
	protected function getBucketThumbPath( $bucket ) {
1312
		$thumbName = $this->getBucketThumbName( $bucket );
1313
		return $this->getThumbPath( $thumbName );
1314
	}
1315
1316
	/**
1317
	 * Returns the name of the thumb for a given bucket
1318
	 * @param int $bucket
1319
	 * @return string
1320
	 */
1321
	protected function getBucketThumbName( $bucket ) {
1322
		return $this->thumbName( [ 'physicalWidth' => $bucket ] );
1323
	}
1324
1325
	/**
1326
	 * Creates a temp FS file with the same extension and the thumbnail
1327
	 * @param string $thumbPath Thumbnail path
1328
	 * @return TempFSFile
1329
	 */
1330
	protected function makeTransformTmpFile( $thumbPath ) {
1331
		$thumbExt = FileBackend::extensionFromPath( $thumbPath );
1332
		return TempFSFile::factory( 'transform_', $thumbExt );
1333
	}
1334
1335
	/**
1336
	 * @param string $thumbName Thumbnail name
1337
	 * @param string $dispositionType Type of disposition (either "attachment" or "inline")
1338
	 * @return string Content-Disposition header value
1339
	 */
1340
	function getThumbDisposition( $thumbName, $dispositionType = 'inline' ) {
1341
		$fileName = $this->name; // file name to suggest
1342
		$thumbExt = FileBackend::extensionFromPath( $thumbName );
1343
		if ( $thumbExt != '' && $thumbExt !== $this->getExtension() ) {
1344
			$fileName .= ".$thumbExt";
1345
		}
1346
1347
		return FileBackend::makeContentDisposition( $dispositionType, $fileName );
1348
	}
1349
1350
	/**
1351
	 * Hook into transform() to allow migration of thumbnail files
1352
	 * STUB
1353
	 * Overridden by LocalFile
1354
	 * @param string $thumbName
1355
	 */
1356
	function migrateThumbFile( $thumbName ) {
1357
	}
1358
1359
	/**
1360
	 * Get a MediaHandler instance for this file
1361
	 *
1362
	 * @return MediaHandler|bool Registered MediaHandler for file's MIME type
1363
	 *   or false if none found
1364
	 */
1365
	function getHandler() {
1366
		if ( !isset( $this->handler ) ) {
1367
			$this->handler = MediaHandler::getHandler( $this->getMimeType() );
1368
		}
1369
1370
		return $this->handler;
1371
	}
1372
1373
	/**
1374
	 * Get a ThumbnailImage representing a file type icon
1375
	 *
1376
	 * @return ThumbnailImage
1377
	 */
1378
	function iconThumb() {
1379
		global $wgResourceBasePath, $IP;
1380
		$assetsPath = "$wgResourceBasePath/resources/assets/file-type-icons/";
1381
		$assetsDirectory = "$IP/resources/assets/file-type-icons/";
1382
1383
		$try = [ 'fileicon-' . $this->getExtension() . '.png', 'fileicon.png' ];
1384
		foreach ( $try as $icon ) {
1385
			if ( file_exists( $assetsDirectory . $icon ) ) { // always FS
1386
				$params = [ 'width' => 120, 'height' => 120 ];
1387
1388
				return new ThumbnailImage( $this, $assetsPath . $icon, false, $params );
1389
			}
1390
		}
1391
1392
		return null;
1393
	}
1394
1395
	/**
1396
	 * Get last thumbnailing error.
1397
	 * Largely obsolete.
1398
	 * @return string
1399
	 */
1400
	function getLastError() {
1401
		return $this->lastError;
1402
	}
1403
1404
	/**
1405
	 * Get all thumbnail names previously generated for this file
1406
	 * STUB
1407
	 * Overridden by LocalFile
1408
	 * @return array
1409
	 */
1410
	function getThumbnails() {
1411
		return [];
1412
	}
1413
1414
	/**
1415
	 * Purge shared caches such as thumbnails and DB data caching
1416
	 * STUB
1417
	 * Overridden by LocalFile
1418
	 * @param array $options Options, which include:
1419
	 *   'forThumbRefresh' : The purging is only to refresh thumbnails
1420
	 */
1421
	function purgeCache( $options = [] ) {
1422
	}
1423
1424
	/**
1425
	 * Purge the file description page, but don't go after
1426
	 * pages using the file. Use when modifying file history
1427
	 * but not the current data.
1428
	 */
1429
	function purgeDescription() {
1430
		$title = $this->getTitle();
1431
		if ( $title ) {
1432
			$title->invalidateCache();
1433
			$title->purgeSquid();
1434
		}
1435
	}
1436
1437
	/**
1438
	 * Purge metadata and all affected pages when the file is created,
1439
	 * deleted, or majorly updated.
1440
	 */
1441
	function purgeEverything() {
1442
		// Delete thumbnails and refresh file metadata cache
1443
		$this->purgeCache();
1444
		$this->purgeDescription();
1445
1446
		// Purge cache of all pages using this file
1447
		$title = $this->getTitle();
1448
		if ( $title ) {
1449
			DeferredUpdates::addUpdate( new HTMLCacheUpdate( $title, 'imagelinks' ) );
1450
		}
1451
	}
1452
1453
	/**
1454
	 * Return a fragment of the history of file.
1455
	 *
1456
	 * STUB
1457
	 * @param int $limit Limit of rows to return
1458
	 * @param string $start Only revisions older than $start will be returned
1459
	 * @param string $end Only revisions newer than $end will be returned
1460
	 * @param bool $inc Include the endpoints of the time range
1461
	 *
1462
	 * @return File[]
1463
	 */
1464
	function getHistory( $limit = null, $start = null, $end = null, $inc = true ) {
1465
		return [];
1466
	}
1467
1468
	/**
1469
	 * Return the history of this file, line by line. Starts with current version,
1470
	 * then old versions. Should return an object similar to an image/oldimage
1471
	 * database row.
1472
	 *
1473
	 * STUB
1474
	 * Overridden in LocalFile
1475
	 * @return bool
1476
	 */
1477
	public function nextHistoryLine() {
1478
		return false;
1479
	}
1480
1481
	/**
1482
	 * Reset the history pointer to the first element of the history.
1483
	 * Always call this function after using nextHistoryLine() to free db resources
1484
	 * STUB
1485
	 * Overridden in LocalFile.
1486
	 */
1487
	public function resetHistory() {
1488
	}
1489
1490
	/**
1491
	 * Get the filename hash component of the directory including trailing slash,
1492
	 * e.g. f/fa/
1493
	 * If the repository is not hashed, returns an empty string.
1494
	 *
1495
	 * @return string
1496
	 */
1497
	function getHashPath() {
1498
		if ( !isset( $this->hashPath ) ) {
1499
			$this->assertRepoDefined();
1500
			$this->hashPath = $this->repo->getHashPath( $this->getName() );
1501
		}
1502
1503
		return $this->hashPath;
1504
	}
1505
1506
	/**
1507
	 * Get the path of the file relative to the public zone root.
1508
	 * This function is overridden in OldLocalFile to be like getArchiveRel().
1509
	 *
1510
	 * @return string
1511
	 */
1512
	function getRel() {
1513
		return $this->getHashPath() . $this->getName();
1514
	}
1515
1516
	/**
1517
	 * Get the path of an archived file relative to the public zone root
1518
	 *
1519
	 * @param bool|string $suffix If not false, the name of an archived thumbnail file
1520
	 *
1521
	 * @return string
1522
	 */
1523 View Code Duplication
	function getArchiveRel( $suffix = false ) {
1524
		$path = 'archive/' . $this->getHashPath();
1525
		if ( $suffix === false ) {
1526
			$path = substr( $path, 0, -1 );
1527
		} else {
1528
			$path .= $suffix;
1529
		}
1530
1531
		return $path;
1532
	}
1533
1534
	/**
1535
	 * Get the path, relative to the thumbnail zone root, of the
1536
	 * thumbnail directory or a particular file if $suffix is specified
1537
	 *
1538
	 * @param bool|string $suffix If not false, the name of a thumbnail file
1539
	 * @return string
1540
	 */
1541
	function getThumbRel( $suffix = false ) {
1542
		$path = $this->getRel();
1543
		if ( $suffix !== false ) {
1544
			$path .= '/' . $suffix;
1545
		}
1546
1547
		return $path;
1548
	}
1549
1550
	/**
1551
	 * Get urlencoded path of the file relative to the public zone root.
1552
	 * This function is overridden in OldLocalFile to be like getArchiveUrl().
1553
	 *
1554
	 * @return string
1555
	 */
1556
	function getUrlRel() {
1557
		return $this->getHashPath() . rawurlencode( $this->getName() );
1558
	}
1559
1560
	/**
1561
	 * Get the path, relative to the thumbnail zone root, for an archived file's thumbs directory
1562
	 * or a specific thumb if the $suffix is given.
1563
	 *
1564
	 * @param string $archiveName The timestamped name of an archived image
1565
	 * @param bool|string $suffix If not false, the name of a thumbnail file
1566
	 * @return string
1567
	 */
1568 View Code Duplication
	function getArchiveThumbRel( $archiveName, $suffix = false ) {
1569
		$path = 'archive/' . $this->getHashPath() . $archiveName . "/";
1570
		if ( $suffix === false ) {
1571
			$path = substr( $path, 0, -1 );
1572
		} else {
1573
			$path .= $suffix;
1574
		}
1575
1576
		return $path;
1577
	}
1578
1579
	/**
1580
	 * Get the path of the archived file.
1581
	 *
1582
	 * @param bool|string $suffix If not false, the name of an archived file.
1583
	 * @return string
1584
	 */
1585
	function getArchivePath( $suffix = false ) {
1586
		$this->assertRepoDefined();
1587
1588
		return $this->repo->getZonePath( 'public' ) . '/' . $this->getArchiveRel( $suffix );
1589
	}
1590
1591
	/**
1592
	 * Get the path of an archived file's thumbs, or a particular thumb if $suffix is specified
1593
	 *
1594
	 * @param string $archiveName The timestamped name of an archived image
1595
	 * @param bool|string $suffix If not false, the name of a thumbnail file
1596
	 * @return string
1597
	 */
1598
	function getArchiveThumbPath( $archiveName, $suffix = false ) {
1599
		$this->assertRepoDefined();
1600
1601
		return $this->repo->getZonePath( 'thumb' ) . '/' .
1602
		$this->getArchiveThumbRel( $archiveName, $suffix );
1603
	}
1604
1605
	/**
1606
	 * Get the path of the thumbnail directory, or a particular file if $suffix is specified
1607
	 *
1608
	 * @param bool|string $suffix If not false, the name of a thumbnail file
1609
	 * @return string
1610
	 */
1611
	function getThumbPath( $suffix = false ) {
1612
		$this->assertRepoDefined();
1613
1614
		return $this->repo->getZonePath( 'thumb' ) . '/' . $this->getThumbRel( $suffix );
1615
	}
1616
1617
	/**
1618
	 * Get the path of the transcoded directory, or a particular file if $suffix is specified
1619
	 *
1620
	 * @param bool|string $suffix If not false, the name of a media file
1621
	 * @return string
1622
	 */
1623
	function getTranscodedPath( $suffix = false ) {
1624
		$this->assertRepoDefined();
1625
1626
		return $this->repo->getZonePath( 'transcoded' ) . '/' . $this->getThumbRel( $suffix );
1627
	}
1628
1629
	/**
1630
	 * Get the URL of the archive directory, or a particular file if $suffix is specified
1631
	 *
1632
	 * @param bool|string $suffix If not false, the name of an archived file
1633
	 * @return string
1634
	 */
1635 View Code Duplication
	function getArchiveUrl( $suffix = false ) {
1636
		$this->assertRepoDefined();
1637
		$ext = $this->getExtension();
1638
		$path = $this->repo->getZoneUrl( 'public', $ext ) . '/archive/' . $this->getHashPath();
1639
		if ( $suffix === false ) {
1640
			$path = substr( $path, 0, -1 );
1641
		} else {
1642
			$path .= rawurlencode( $suffix );
1643
		}
1644
1645
		return $path;
1646
	}
1647
1648
	/**
1649
	 * Get the URL of the archived file's thumbs, or a particular thumb if $suffix is specified
1650
	 *
1651
	 * @param string $archiveName The timestamped name of an archived image
1652
	 * @param bool|string $suffix If not false, the name of a thumbnail file
1653
	 * @return string
1654
	 */
1655 View Code Duplication
	function getArchiveThumbUrl( $archiveName, $suffix = false ) {
1656
		$this->assertRepoDefined();
1657
		$ext = $this->getExtension();
1658
		$path = $this->repo->getZoneUrl( 'thumb', $ext ) . '/archive/' .
1659
			$this->getHashPath() . rawurlencode( $archiveName ) . "/";
1660
		if ( $suffix === false ) {
1661
			$path = substr( $path, 0, -1 );
1662
		} else {
1663
			$path .= rawurlencode( $suffix );
1664
		}
1665
1666
		return $path;
1667
	}
1668
1669
	/**
1670
	 * Get the URL of the zone directory, or a particular file if $suffix is specified
1671
	 *
1672
	 * @param string $zone Name of requested zone
1673
	 * @param bool|string $suffix If not false, the name of a file in zone
1674
	 * @return string Path
1675
	 */
1676
	function getZoneUrl( $zone, $suffix = false ) {
1677
		$this->assertRepoDefined();
1678
		$ext = $this->getExtension();
1679
		$path = $this->repo->getZoneUrl( $zone, $ext ) . '/' . $this->getUrlRel();
1680
		if ( $suffix !== false ) {
1681
			$path .= '/' . rawurlencode( $suffix );
1682
		}
1683
1684
		return $path;
1685
	}
1686
1687
	/**
1688
	 * Get the URL of the thumbnail directory, or a particular file if $suffix is specified
1689
	 *
1690
	 * @param bool|string $suffix If not false, the name of a thumbnail file
1691
	 * @return string Path
1692
	 */
1693
	function getThumbUrl( $suffix = false ) {
1694
		return $this->getZoneUrl( 'thumb', $suffix );
1695
	}
1696
1697
	/**
1698
	 * Get the URL of the transcoded directory, or a particular file if $suffix is specified
1699
	 *
1700
	 * @param bool|string $suffix If not false, the name of a media file
1701
	 * @return string Path
1702
	 */
1703
	function getTranscodedUrl( $suffix = false ) {
1704
		return $this->getZoneUrl( 'transcoded', $suffix );
1705
	}
1706
1707
	/**
1708
	 * Get the public zone virtual URL for a current version source file
1709
	 *
1710
	 * @param bool|string $suffix If not false, the name of a thumbnail file
1711
	 * @return string
1712
	 */
1713 View Code Duplication
	function getVirtualUrl( $suffix = false ) {
1714
		$this->assertRepoDefined();
1715
		$path = $this->repo->getVirtualUrl() . '/public/' . $this->getUrlRel();
1716
		if ( $suffix !== false ) {
1717
			$path .= '/' . rawurlencode( $suffix );
1718
		}
1719
1720
		return $path;
1721
	}
1722
1723
	/**
1724
	 * Get the public zone virtual URL for an archived version source file
1725
	 *
1726
	 * @param bool|string $suffix If not false, the name of a thumbnail file
1727
	 * @return string
1728
	 */
1729 View Code Duplication
	function getArchiveVirtualUrl( $suffix = false ) {
1730
		$this->assertRepoDefined();
1731
		$path = $this->repo->getVirtualUrl() . '/public/archive/' . $this->getHashPath();
1732
		if ( $suffix === false ) {
1733
			$path = substr( $path, 0, -1 );
1734
		} else {
1735
			$path .= rawurlencode( $suffix );
1736
		}
1737
1738
		return $path;
1739
	}
1740
1741
	/**
1742
	 * Get the virtual URL for a thumbnail file or directory
1743
	 *
1744
	 * @param bool|string $suffix If not false, the name of a thumbnail file
1745
	 * @return string
1746
	 */
1747 View Code Duplication
	function getThumbVirtualUrl( $suffix = false ) {
1748
		$this->assertRepoDefined();
1749
		$path = $this->repo->getVirtualUrl() . '/thumb/' . $this->getUrlRel();
1750
		if ( $suffix !== false ) {
1751
			$path .= '/' . rawurlencode( $suffix );
1752
		}
1753
1754
		return $path;
1755
	}
1756
1757
	/**
1758
	 * @return bool
1759
	 */
1760
	function isHashed() {
1761
		$this->assertRepoDefined();
1762
1763
		return (bool)$this->repo->getHashLevels();
1764
	}
1765
1766
	/**
1767
	 * @throws MWException
1768
	 */
1769
	function readOnlyError() {
1770
		throw new MWException( get_class( $this ) . ': write operations are not supported' );
1771
	}
1772
1773
	/**
1774
	 * Record a file upload in the upload log and the image table
1775
	 * STUB
1776
	 * Overridden by LocalFile
1777
	 * @param string $oldver
1778
	 * @param string $desc
1779
	 * @param string $license
1780
	 * @param string $copyStatus
1781
	 * @param string $source
1782
	 * @param bool $watch
1783
	 * @param string|bool $timestamp
1784
	 * @param null|User $user User object or null to use $wgUser
1785
	 * @return bool
1786
	 * @throws MWException
1787
	 */
1788
	function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '',
1789
		$watch = false, $timestamp = false, User $user = null
1790
	) {
1791
		$this->readOnlyError();
1792
	}
1793
1794
	/**
1795
	 * Move or copy a file to its public location. If a file exists at the
1796
	 * destination, move it to an archive. Returns a FileRepoStatus object with
1797
	 * the archive name in the "value" member on success.
1798
	 *
1799
	 * The archive name should be passed through to recordUpload for database
1800
	 * registration.
1801
	 *
1802
	 * Options to $options include:
1803
	 *   - headers : name/value map of HTTP headers to use in response to GET/HEAD requests
1804
	 *
1805
	 * @param string|FSFile $src Local filesystem path to the source image
1806
	 * @param int $flags A bitwise combination of:
1807
	 *   File::DELETE_SOURCE    Delete the source file, i.e. move rather than copy
1808
	 * @param array $options Optional additional parameters
1809
	 * @return FileRepoStatus On success, the value member contains the
1810
	 *   archive name, or an empty string if it was a new file.
1811
	 *
1812
	 * STUB
1813
	 * Overridden by LocalFile
1814
	 */
1815
	function publish( $src, $flags = 0, array $options = [] ) {
1816
		$this->readOnlyError();
1817
	}
1818
1819
	/**
1820
	 * @param bool|IContextSource $context Context to use (optional)
1821
	 * @return bool
1822
	 */
1823
	function formatMetadata( $context = false ) {
1824
		if ( !$this->getHandler() ) {
1825
			return false;
1826
		}
1827
1828
		return $this->getHandler()->formatMetadata( $this, $context );
1829
	}
1830
1831
	/**
1832
	 * Returns true if the file comes from the local file repository.
1833
	 *
1834
	 * @return bool
1835
	 */
1836
	function isLocal() {
1837
		return $this->repo && $this->repo->isLocal();
1838
	}
1839
1840
	/**
1841
	 * Returns the name of the repository.
1842
	 *
1843
	 * @return string
1844
	 */
1845
	function getRepoName() {
1846
		return $this->repo ? $this->repo->getName() : 'unknown';
1847
	}
1848
1849
	/**
1850
	 * Returns the repository
1851
	 *
1852
	 * @return FileRepo|LocalRepo|bool
1853
	 */
1854
	function getRepo() {
1855
		return $this->repo;
1856
	}
1857
1858
	/**
1859
	 * Returns true if the image is an old version
1860
	 * STUB
1861
	 *
1862
	 * @return bool
1863
	 */
1864
	function isOld() {
1865
		return false;
1866
	}
1867
1868
	/**
1869
	 * Is this file a "deleted" file in a private archive?
1870
	 * STUB
1871
	 *
1872
	 * @param int $field One of DELETED_* bitfield constants
1873
	 * @return bool
1874
	 */
1875
	function isDeleted( $field ) {
1876
		return false;
1877
	}
1878
1879
	/**
1880
	 * Return the deletion bitfield
1881
	 * STUB
1882
	 * @return int
1883
	 */
1884
	function getVisibility() {
1885
		return 0;
1886
	}
1887
1888
	/**
1889
	 * Was this file ever deleted from the wiki?
1890
	 *
1891
	 * @return bool
1892
	 */
1893
	function wasDeleted() {
1894
		$title = $this->getTitle();
1895
1896
		return $title && $title->isDeletedQuick();
1897
	}
1898
1899
	/**
1900
	 * Move file to the new title
1901
	 *
1902
	 * Move current, old version and all thumbnails
1903
	 * to the new filename. Old file is deleted.
1904
	 *
1905
	 * Cache purging is done; checks for validity
1906
	 * and logging are caller's responsibility
1907
	 *
1908
	 * @param Title $target New file name
1909
	 * @return FileRepoStatus
1910
	 */
1911
	function move( $target ) {
1912
		$this->readOnlyError();
1913
	}
1914
1915
	/**
1916
	 * Delete all versions of the file.
1917
	 *
1918
	 * Moves the files into an archive directory (or deletes them)
1919
	 * and removes the database rows.
1920
	 *
1921
	 * Cache purging is done; logging is caller's responsibility.
1922
	 *
1923
	 * @param string $reason
1924
	 * @param bool $suppress Hide content from sysops?
1925
	 * @param User|null $user
1926
	 * @return FileRepoStatus
1927
	 * STUB
1928
	 * Overridden by LocalFile
1929
	 */
1930
	function delete( $reason, $suppress = false, $user = null ) {
1931
		$this->readOnlyError();
1932
	}
1933
1934
	/**
1935
	 * Restore all or specified deleted revisions to the given file.
1936
	 * Permissions and logging are left to the caller.
1937
	 *
1938
	 * May throw database exceptions on error.
1939
	 *
1940
	 * @param array $versions Set of record ids of deleted items to restore,
1941
	 *   or empty to restore all revisions.
1942
	 * @param bool $unsuppress Remove restrictions on content upon restoration?
1943
	 * @return int|bool The number of file revisions restored if successful,
1944
	 *   or false on failure
1945
	 * STUB
1946
	 * Overridden by LocalFile
1947
	 */
1948
	function restore( $versions = [], $unsuppress = false ) {
1949
		$this->readOnlyError();
1950
	}
1951
1952
	/**
1953
	 * Returns 'true' if this file is a type which supports multiple pages,
1954
	 * e.g. DJVU or PDF. Note that this may be true even if the file in
1955
	 * question only has a single page.
1956
	 *
1957
	 * @return bool
1958
	 */
1959
	function isMultipage() {
1960
		return $this->getHandler() && $this->handler->isMultiPage( $this );
1961
	}
1962
1963
	/**
1964
	 * Returns the number of pages of a multipage document, or false for
1965
	 * documents which aren't multipage documents
1966
	 *
1967
	 * @return bool|int
1968
	 */
1969 View Code Duplication
	function pageCount() {
1970
		if ( !isset( $this->pageCount ) ) {
1971
			if ( $this->getHandler() && $this->handler->isMultiPage( $this ) ) {
1972
				$this->pageCount = $this->handler->pageCount( $this );
0 ignored issues
show
Documentation Bug introduced by
The property $pageCount was declared of type string, but $this->handler->pageCount($this) is of type boolean. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
1973
			} else {
1974
				$this->pageCount = false;
0 ignored issues
show
Documentation Bug introduced by
The property $pageCount was declared of type string, but false is of type false. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
1975
			}
1976
		}
1977
1978
		return $this->pageCount;
1979
	}
1980
1981
	/**
1982
	 * Calculate the height of a thumbnail using the source and destination width
1983
	 *
1984
	 * @param int $srcWidth
1985
	 * @param int $srcHeight
1986
	 * @param int $dstWidth
1987
	 *
1988
	 * @return int
1989
	 */
1990
	static function scaleHeight( $srcWidth, $srcHeight, $dstWidth ) {
1991
		// Exact integer multiply followed by division
1992
		if ( $srcWidth == 0 ) {
1993
			return 0;
1994
		} else {
1995
			return round( $srcHeight * $dstWidth / $srcWidth );
1996
		}
1997
	}
1998
1999
	/**
2000
	 * Get an image size array like that returned by getImageSize(), or false if it
2001
	 * can't be determined. Loads the image size directly from the file ignoring caches.
2002
	 *
2003
	 * @note Use getWidth()/getHeight() instead of this method unless you have a
2004
	 *  a good reason. This method skips all caches.
2005
	 *
2006
	 * @param string $filePath The path to the file (e.g. From getLocalPathRef() )
2007
	 * @return array The width, followed by height, with optionally more things after
2008
	 */
2009
	function getImageSize( $filePath ) {
2010
		if ( !$this->getHandler() ) {
2011
			return false;
2012
		}
2013
2014
		return $this->getHandler()->getImageSize( $this, $filePath );
2015
	}
2016
2017
	/**
2018
	 * Get the URL of the image description page. May return false if it is
2019
	 * unknown or not applicable.
2020
	 *
2021
	 * @return string
2022
	 */
2023
	function getDescriptionUrl() {
2024
		if ( $this->repo ) {
2025
			return $this->repo->getDescriptionUrl( $this->getName() );
2026
		} else {
2027
			return false;
2028
		}
2029
	}
2030
2031
	/**
2032
	 * Get the HTML text of the description page, if available
2033
	 *
2034
	 * @param bool|Language $lang Optional language to fetch description in
2035
	 * @return string
2036
	 */
2037
	function getDescriptionText( $lang = false ) {
2038
		global $wgLang;
2039
2040
		if ( !$this->repo || !$this->repo->fetchDescription ) {
2041
			return false;
2042
		}
2043
2044
		$lang = $lang ?: $wgLang;
2045
2046
		$renderUrl = $this->repo->getDescriptionRenderUrl( $this->getName(), $lang->getCode() );
2047
		if ( $renderUrl ) {
2048
			$cache = ObjectCache::getMainWANInstance();
2049
			$key = $this->repo->getLocalCacheKey(
2050
				'RemoteFileDescription',
2051
				'url',
2052
				$lang->getCode(),
2053
				$this->getName()
2054
			);
2055
2056
			return $cache->getWithSetCallback(
2057
				$key,
2058
				$this->repo->descriptionCacheExpiry ?: $cache::TTL_UNCACHEABLE,
2059 View Code Duplication
				function ( $oldValue, &$ttl, array &$setOpts ) use ( $renderUrl ) {
2060
					wfDebug( "Fetching shared description from $renderUrl\n" );
2061
					$res = Http::get( $renderUrl, [], __METHOD__ );
2062
					if ( !$res ) {
2063
						$ttl = WANObjectCache::TTL_UNCACHEABLE;
2064
					}
2065
2066
					return $res;
2067
				}
2068
			);
2069
		}
2070
2071
		return false;
2072
	}
2073
2074
	/**
2075
	 * Get description of file revision
2076
	 * STUB
2077
	 *
2078
	 * @param int $audience One of:
2079
	 *   File::FOR_PUBLIC       to be displayed to all users
2080
	 *   File::FOR_THIS_USER    to be displayed to the given user
2081
	 *   File::RAW              get the description regardless of permissions
2082
	 * @param User $user User object to check for, only if FOR_THIS_USER is
2083
	 *   passed to the $audience parameter
2084
	 * @return string
2085
	 */
2086
	function getDescription( $audience = self::FOR_PUBLIC, User $user = null ) {
2087
		return null;
2088
	}
2089
2090
	/**
2091
	 * Get the 14-character timestamp of the file upload
2092
	 *
2093
	 * @return string|bool TS_MW timestamp or false on failure
2094
	 */
2095
	function getTimestamp() {
2096
		$this->assertRepoDefined();
2097
2098
		return $this->repo->getFileTimestamp( $this->getPath() );
2099
	}
2100
2101
	/**
2102
	 * Returns the timestamp (in TS_MW format) of the last change of the description page.
2103
	 * Returns false if the file does not have a description page, or retrieving the timestamp
2104
	 * would be expensive.
2105
	 * @since 1.25
2106
	 * @return string|bool
2107
	 */
2108
	public function getDescriptionTouched() {
2109
		return false;
2110
	}
2111
2112
	/**
2113
	 * Get the SHA-1 base 36 hash of the file
2114
	 *
2115
	 * @return string
2116
	 */
2117
	function getSha1() {
2118
		$this->assertRepoDefined();
2119
2120
		return $this->repo->getFileSha1( $this->getPath() );
2121
	}
2122
2123
	/**
2124
	 * Get the deletion archive key, "<sha1>.<ext>"
2125
	 *
2126
	 * @return string
2127
	 */
2128
	function getStorageKey() {
2129
		$hash = $this->getSha1();
2130
		if ( !$hash ) {
2131
			return false;
2132
		}
2133
		$ext = $this->getExtension();
2134
		$dotExt = $ext === '' ? '' : ".$ext";
2135
2136
		return $hash . $dotExt;
2137
	}
2138
2139
	/**
2140
	 * Determine if the current user is allowed to view a particular
2141
	 * field of this file, if it's marked as deleted.
2142
	 * STUB
2143
	 * @param int $field
2144
	 * @param User $user User object to check, or null to use $wgUser
2145
	 * @return bool
2146
	 */
2147
	function userCan( $field, User $user = null ) {
2148
		return true;
2149
	}
2150
2151
	/**
2152
	 * @return array HTTP header name/value map to use for HEAD/GET request responses
2153
	 */
2154
	function getStreamHeaders() {
2155
		$handler = $this->getHandler();
2156
		if ( $handler ) {
2157
			return $handler->getStreamHeaders( $this->getMetadata() );
2158
		} else {
2159
			return [];
2160
		}
2161
	}
2162
2163
	/**
2164
	 * @return string
2165
	 */
2166
	function getLongDesc() {
2167
		$handler = $this->getHandler();
2168
		if ( $handler ) {
2169
			return $handler->getLongDesc( $this );
2170
		} else {
2171
			return MediaHandler::getGeneralLongDesc( $this );
2172
		}
2173
	}
2174
2175
	/**
2176
	 * @return string
2177
	 */
2178
	function getShortDesc() {
2179
		$handler = $this->getHandler();
2180
		if ( $handler ) {
2181
			return $handler->getShortDesc( $this );
2182
		} else {
2183
			return MediaHandler::getGeneralShortDesc( $this );
2184
		}
2185
	}
2186
2187
	/**
2188
	 * @return string
2189
	 */
2190
	function getDimensionsString() {
2191
		$handler = $this->getHandler();
2192
		if ( $handler ) {
2193
			return $handler->getDimensionsString( $this );
2194
		} else {
2195
			return '';
2196
		}
2197
	}
2198
2199
	/**
2200
	 * @return string
2201
	 */
2202
	function getRedirected() {
2203
		return $this->redirected;
2204
	}
2205
2206
	/**
2207
	 * @return Title|null
2208
	 */
2209
	function getRedirectedTitle() {
2210
		if ( $this->redirected ) {
2211
			if ( !$this->redirectTitle ) {
2212
				$this->redirectTitle = Title::makeTitle( NS_FILE, $this->redirected );
2213
			}
2214
2215
			return $this->redirectTitle;
2216
		}
2217
2218
		return null;
2219
	}
2220
2221
	/**
2222
	 * @param string $from
2223
	 * @return void
2224
	 */
2225
	function redirectedFrom( $from ) {
2226
		$this->redirected = $from;
2227
	}
2228
2229
	/**
2230
	 * @return bool
2231
	 */
2232
	function isMissing() {
2233
		return false;
2234
	}
2235
2236
	/**
2237
	 * Check if this file object is small and can be cached
2238
	 * @return bool
2239
	 */
2240
	public function isCacheable() {
2241
		return true;
2242
	}
2243
2244
	/**
2245
	 * Assert that $this->repo is set to a valid FileRepo instance
2246
	 * @throws MWException
2247
	 */
2248
	protected function assertRepoDefined() {
2249
		if ( !( $this->repo instanceof $this->repoClass ) ) {
2250
			throw new MWException( "A {$this->repoClass} object is not set for this File.\n" );
2251
		}
2252
	}
2253
2254
	/**
2255
	 * Assert that $this->title is set to a Title
2256
	 * @throws MWException
2257
	 */
2258
	protected function assertTitleDefined() {
2259
		if ( !( $this->title instanceof Title ) ) {
2260
			throw new MWException( "A Title object is not set for this File.\n" );
2261
		}
2262
	}
2263
2264
	/**
2265
	 * True if creating thumbnails from the file is large or otherwise resource-intensive.
2266
	 * @return bool
2267
	 */
2268
	public function isExpensiveToThumbnail() {
2269
		$handler = $this->getHandler();
2270
		return $handler ? $handler->isExpensiveToThumbnail( $this ) : false;
2271
	}
2272
2273
	/**
2274
	 * Whether the thumbnails created on the same server as this code is running.
2275
	 * @since 1.25
2276
	 * @return bool
2277
	 */
2278
	public function isTransformedLocally() {
2279
		return true;
2280
	}
2281
}
2282