Issues (4122)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

includes/filerepo/file/File.php (9 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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() );
0 ignored issues
show
It seems like $this->getWidth() targeting File::getWidth() can also be of type boolean; however, File::createThumb() does only seem to accept integer, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
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() );
0 ignored issues
show
It seems like $this->getPath() targeting File::getPath() can also be of type boolean; however, FileRepo::getLocalReference() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
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 ThumbnailImage|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
					// XXX: Pass in the storage path even though we are not rendering anything
1069
					// and the path is supposed to be an FS path. This is due to getScalerType()
1070
					// getting called on the path and clobbering $thumb->getUrl() if it's false.
1071
					$thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
1072
					break;
1073
				}
1074
				// Check if an up-to-date thumbnail already exists...
1075
				wfDebug( __METHOD__ . ": Doing stat for $thumbPath\n" );
1076
				if ( !( $flags & self::RENDER_FORCE ) && $this->repo->fileExists( $thumbPath ) ) {
1077
					$timestamp = $this->repo->getFileTimestamp( $thumbPath );
1078
					if ( $timestamp !== false && $timestamp >= $wgThumbnailEpoch ) {
1079
						// XXX: Pass in the storage path even though we are not rendering anything
1080
						// and the path is supposed to be an FS path. This is due to getScalerType()
1081
						// getting called on the path and clobbering $thumb->getUrl() if it's false.
1082
						$thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
1083
						$thumb->setStoragePath( $thumbPath );
1084
						break;
1085
					}
1086
				} elseif ( $flags & self::RENDER_FORCE ) {
1087
					wfDebug( __METHOD__ . " forcing rendering per flag File::RENDER_FORCE\n" );
1088
				}
1089
1090
				// If the backend is ready-only, don't keep generating thumbnails
1091
				// only to return transformation errors, just return the error now.
1092
				if ( $this->repo->getReadOnlyReason() !== false ) {
1093
					$thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
1094
					break;
1095
				}
1096
			}
1097
1098
			$tmpFile = $this->makeTransformTmpFile( $thumbPath );
0 ignored issues
show
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...
1099
1100
			if ( !$tmpFile ) {
1101
				$thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
1102
			} else {
1103
				$thumb = $this->generateAndSaveThumb( $tmpFile, $params, $flags );
1104
			}
1105
		} while ( false );
1106
1107
		return is_object( $thumb ) ? $thumb : false;
1108
	}
1109
1110
	/**
1111
	 * Generates a thumbnail according to the given parameters and saves it to storage
1112
	 * @param TempFSFile $tmpFile Temporary file where the rendered thumbnail will be saved
1113
	 * @param array $transformParams
1114
	 * @param int $flags
1115
	 * @return bool|MediaTransformOutput
1116
	 */
1117
	public function generateAndSaveThumb( $tmpFile, $transformParams, $flags ) {
1118
		global $wgIgnoreImageErrors;
1119
1120
		$stats = RequestContext::getMain()->getStats();
1121
1122
		$handler = $this->getHandler();
1123
1124
		$normalisedParams = $transformParams;
1125
		$handler->normaliseParams( $this, $normalisedParams );
1126
1127
		$thumbName = $this->thumbName( $normalisedParams );
1128
		$thumbUrl = $this->getThumbUrl( $thumbName );
1129
		$thumbPath = $this->getThumbPath( $thumbName ); // final thumb path
1130
1131
		$tmpThumbPath = $tmpFile->getPath();
1132
1133
		if ( $handler->supportsBucketing() ) {
1134
			$this->generateBucketsIfNeeded( $normalisedParams, $flags );
1135
		}
1136
1137
		$starttime = microtime( true );
1138
1139
		// Actually render the thumbnail...
1140
		$thumb = $handler->doTransform( $this, $tmpThumbPath, $thumbUrl, $transformParams );
1141
		$tmpFile->bind( $thumb ); // keep alive with $thumb
1142
1143
		$statTiming = microtime( true ) - $starttime;
1144
		$stats->timing( 'media.thumbnail.generate.transform', 1000 * $statTiming );
1145
1146
		if ( !$thumb ) { // bad params?
1147
			$thumb = false;
1148
		} elseif ( $thumb->isError() ) { // transform error
1149
			/** @var $thumb MediaTransformError */
1150
			$this->lastError = $thumb->toText();
1151
			// Ignore errors if requested
1152
			if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
1153
				$thumb = $handler->getTransform( $this, $tmpThumbPath, $thumbUrl, $transformParams );
1154
			}
1155
		} elseif ( $this->repo && $thumb->hasFile() && !$thumb->fileIsSource() ) {
1156
			// Copy the thumbnail from the file system into storage...
1157
1158
			$starttime = microtime( true );
1159
1160
			$disposition = $this->getThumbDisposition( $thumbName );
1161
			$status = $this->repo->quickImport( $tmpThumbPath, $thumbPath, $disposition );
1162
			if ( $status->isOK() ) {
1163
				$thumb->setStoragePath( $thumbPath );
1164
			} else {
1165
				$thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $transformParams, $flags );
1166
			}
1167
1168
			$statTiming = microtime( true ) - $starttime;
1169
			$stats->timing( 'media.thumbnail.generate.store', 1000 * $statTiming );
1170
1171
			// Give extensions a chance to do something with this thumbnail...
1172
			Hooks::run( 'FileTransformed', [ $this, $thumb, $tmpThumbPath, $thumbPath ] );
1173
		}
1174
1175
		return $thumb;
1176
	}
1177
1178
	/**
1179
	 * Generates chained bucketed thumbnails if needed
1180
	 * @param array $params
1181
	 * @param int $flags
1182
	 * @return bool Whether at least one bucket was generated
1183
	 */
1184
	protected function generateBucketsIfNeeded( $params, $flags = 0 ) {
1185
		if ( !$this->repo
1186
			|| !isset( $params['physicalWidth'] )
1187
			|| !isset( $params['physicalHeight'] )
1188
		) {
1189
			return false;
1190
		}
1191
1192
		$bucket = $this->getThumbnailBucket( $params['physicalWidth'] );
1193
1194
		if ( !$bucket || $bucket == $params['physicalWidth'] ) {
1195
			return false;
1196
		}
1197
1198
		$bucketPath = $this->getBucketThumbPath( $bucket );
1199
1200
		if ( $this->repo->fileExists( $bucketPath ) ) {
1201
			return false;
1202
		}
1203
1204
		$starttime = microtime( true );
1205
1206
		$params['physicalWidth'] = $bucket;
1207
		$params['width'] = $bucket;
1208
1209
		$params = $this->getHandler()->sanitizeParamsForBucketing( $params );
1210
1211
		$tmpFile = $this->makeTransformTmpFile( $bucketPath );
1212
1213
		if ( !$tmpFile ) {
1214
			return false;
1215
		}
1216
1217
		$thumb = $this->generateAndSaveThumb( $tmpFile, $params, $flags );
1218
1219
		$buckettime = microtime( true ) - $starttime;
1220
1221
		if ( !$thumb || $thumb->isError() ) {
1222
			return false;
1223
		}
1224
1225
		$this->tmpBucketedThumbCache[$bucket] = $tmpFile->getPath();
1226
		// For the caching to work, we need to make the tmp file survive as long as
1227
		// this object exists
1228
		$tmpFile->bind( $this );
1229
1230
		RequestContext::getMain()->getStats()->timing(
1231
			'media.thumbnail.generate.bucket', 1000 * $buckettime );
1232
1233
		return true;
1234
	}
1235
1236
	/**
1237
	 * Returns the most appropriate source image for the thumbnail, given a target thumbnail size
1238
	 * @param array $params
1239
	 * @return array Source path and width/height of the source
1240
	 */
1241
	public function getThumbnailSource( $params ) {
1242
		if ( $this->repo
1243
			&& $this->getHandler()->supportsBucketing()
1244
			&& isset( $params['physicalWidth'] )
1245
			&& $bucket = $this->getThumbnailBucket( $params['physicalWidth'] )
1246
		) {
1247
			if ( $this->getWidth() != 0 ) {
1248
				$bucketHeight = round( $this->getHeight() * ( $bucket / $this->getWidth() ) );
1249
			} else {
1250
				$bucketHeight = 0;
1251
			}
1252
1253
			// Try to avoid reading from storage if the file was generated by this script
1254
			if ( isset( $this->tmpBucketedThumbCache[$bucket] ) ) {
1255
				$tmpPath = $this->tmpBucketedThumbCache[$bucket];
1256
1257
				if ( file_exists( $tmpPath ) ) {
1258
					return [
1259
						'path' => $tmpPath,
1260
						'width' => $bucket,
1261
						'height' => $bucketHeight
1262
					];
1263
				}
1264
			}
1265
1266
			$bucketPath = $this->getBucketThumbPath( $bucket );
1267
1268
			if ( $this->repo->fileExists( $bucketPath ) ) {
1269
				$fsFile = $this->repo->getLocalReference( $bucketPath );
1270
1271
				if ( $fsFile ) {
1272
					return [
1273
						'path' => $fsFile->getPath(),
1274
						'width' => $bucket,
1275
						'height' => $bucketHeight
1276
					];
1277
				}
1278
			}
1279
		}
1280
1281
		// Thumbnailing a very large file could result in network saturation if
1282
		// everyone does it at once.
1283
		if ( $this->getSize() >= 1e7 ) { // 10MB
1284
			$that = $this;
1285
			$work = new PoolCounterWorkViaCallback( 'GetLocalFileCopy', sha1( $this->getName() ),
1286
				[
1287
					'doWork' => function () use ( $that ) {
1288
						return $that->getLocalRefPath();
1289
					}
1290
				]
1291
			);
1292
			$srcPath = $work->execute();
1293
		} else {
1294
			$srcPath = $this->getLocalRefPath();
1295
		}
1296
1297
		// Original file
1298
		return [
1299
			'path' => $srcPath,
1300
			'width' => $this->getWidth(),
1301
			'height' => $this->getHeight()
1302
		];
1303
	}
1304
1305
	/**
1306
	 * Returns the repo path of the thumb for a given bucket
1307
	 * @param int $bucket
1308
	 * @return string
1309
	 */
1310
	protected function getBucketThumbPath( $bucket ) {
1311
		$thumbName = $this->getBucketThumbName( $bucket );
1312
		return $this->getThumbPath( $thumbName );
1313
	}
1314
1315
	/**
1316
	 * Returns the name of the thumb for a given bucket
1317
	 * @param int $bucket
1318
	 * @return string
1319
	 */
1320
	protected function getBucketThumbName( $bucket ) {
1321
		return $this->thumbName( [ 'physicalWidth' => $bucket ] );
1322
	}
1323
1324
	/**
1325
	 * Creates a temp FS file with the same extension and the thumbnail
1326
	 * @param string $thumbPath Thumbnail path
1327
	 * @return TempFSFile|null
1328
	 */
1329
	protected function makeTransformTmpFile( $thumbPath ) {
1330
		$thumbExt = FileBackend::extensionFromPath( $thumbPath );
1331
		return TempFSFile::factory( 'transform_', $thumbExt, wfTempDir() );
1332
	}
1333
1334
	/**
1335
	 * @param string $thumbName Thumbnail name
1336
	 * @param string $dispositionType Type of disposition (either "attachment" or "inline")
1337
	 * @return string Content-Disposition header value
1338
	 */
1339
	function getThumbDisposition( $thumbName, $dispositionType = 'inline' ) {
1340
		$fileName = $this->name; // file name to suggest
1341
		$thumbExt = FileBackend::extensionFromPath( $thumbName );
1342
		if ( $thumbExt != '' && $thumbExt !== $this->getExtension() ) {
1343
			$fileName .= ".$thumbExt";
1344
		}
1345
1346
		return FileBackend::makeContentDisposition( $dispositionType, $fileName );
1347
	}
1348
1349
	/**
1350
	 * Hook into transform() to allow migration of thumbnail files
1351
	 * STUB
1352
	 * Overridden by LocalFile
1353
	 * @param string $thumbName
1354
	 */
1355
	function migrateThumbFile( $thumbName ) {
1356
	}
1357
1358
	/**
1359
	 * Get a MediaHandler instance for this file
1360
	 *
1361
	 * @return MediaHandler|bool Registered MediaHandler for file's MIME type
1362
	 *   or false if none found
1363
	 */
1364
	function getHandler() {
1365
		if ( !isset( $this->handler ) ) {
1366
			$this->handler = MediaHandler::getHandler( $this->getMimeType() );
0 ignored issues
show
Documentation Bug introduced by
It seems like \MediaHandler::getHandler($this->getMimeType()) can also be of type false. However, the property $handler is declared as type object<MediaHandler>. Maybe add an additional type 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 mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1367
		}
1368
1369
		return $this->handler;
1370
	}
1371
1372
	/**
1373
	 * Get a ThumbnailImage representing a file type icon
1374
	 *
1375
	 * @return ThumbnailImage
1376
	 */
1377
	function iconThumb() {
1378
		global $wgResourceBasePath, $IP;
1379
		$assetsPath = "$wgResourceBasePath/resources/assets/file-type-icons/";
1380
		$assetsDirectory = "$IP/resources/assets/file-type-icons/";
1381
1382
		$try = [ 'fileicon-' . $this->getExtension() . '.png', 'fileicon.png' ];
1383
		foreach ( $try as $icon ) {
1384
			if ( file_exists( $assetsDirectory . $icon ) ) { // always FS
1385
				$params = [ 'width' => 120, 'height' => 120 ];
1386
1387
				return new ThumbnailImage( $this, $assetsPath . $icon, false, $params );
1388
			}
1389
		}
1390
1391
		return null;
1392
	}
1393
1394
	/**
1395
	 * Get last thumbnailing error.
1396
	 * Largely obsolete.
1397
	 * @return string
1398
	 */
1399
	function getLastError() {
1400
		return $this->lastError;
1401
	}
1402
1403
	/**
1404
	 * Get all thumbnail names previously generated for this file
1405
	 * STUB
1406
	 * Overridden by LocalFile
1407
	 * @return array
1408
	 */
1409
	function getThumbnails() {
1410
		return [];
1411
	}
1412
1413
	/**
1414
	 * Purge shared caches such as thumbnails and DB data caching
1415
	 * STUB
1416
	 * Overridden by LocalFile
1417
	 * @param array $options Options, which include:
1418
	 *   'forThumbRefresh' : The purging is only to refresh thumbnails
1419
	 */
1420
	function purgeCache( $options = [] ) {
1421
	}
1422
1423
	/**
1424
	 * Purge the file description page, but don't go after
1425
	 * pages using the file. Use when modifying file history
1426
	 * but not the current data.
1427
	 */
1428
	function purgeDescription() {
1429
		$title = $this->getTitle();
1430
		if ( $title ) {
1431
			$title->invalidateCache();
1432
			$title->purgeSquid();
1433
		}
1434
	}
1435
1436
	/**
1437
	 * Purge metadata and all affected pages when the file is created,
1438
	 * deleted, or majorly updated.
1439
	 */
1440
	function purgeEverything() {
1441
		// Delete thumbnails and refresh file metadata cache
1442
		$this->purgeCache();
1443
		$this->purgeDescription();
1444
1445
		// Purge cache of all pages using this file
1446
		$title = $this->getTitle();
1447
		if ( $title ) {
1448
			DeferredUpdates::addUpdate( new HTMLCacheUpdate( $title, 'imagelinks' ) );
1449
		}
1450
	}
1451
1452
	/**
1453
	 * Return a fragment of the history of file.
1454
	 *
1455
	 * STUB
1456
	 * @param int $limit Limit of rows to return
1457
	 * @param string $start Only revisions older than $start will be returned
1458
	 * @param string $end Only revisions newer than $end will be returned
1459
	 * @param bool $inc Include the endpoints of the time range
1460
	 *
1461
	 * @return File[]
1462
	 */
1463
	function getHistory( $limit = null, $start = null, $end = null, $inc = true ) {
1464
		return [];
1465
	}
1466
1467
	/**
1468
	 * Return the history of this file, line by line. Starts with current version,
1469
	 * then old versions. Should return an object similar to an image/oldimage
1470
	 * database row.
1471
	 *
1472
	 * STUB
1473
	 * Overridden in LocalFile
1474
	 * @return bool
1475
	 */
1476
	public function nextHistoryLine() {
1477
		return false;
1478
	}
1479
1480
	/**
1481
	 * Reset the history pointer to the first element of the history.
1482
	 * Always call this function after using nextHistoryLine() to free db resources
1483
	 * STUB
1484
	 * Overridden in LocalFile.
1485
	 */
1486
	public function resetHistory() {
1487
	}
1488
1489
	/**
1490
	 * Get the filename hash component of the directory including trailing slash,
1491
	 * e.g. f/fa/
1492
	 * If the repository is not hashed, returns an empty string.
1493
	 *
1494
	 * @return string
1495
	 */
1496
	function getHashPath() {
1497
		if ( !isset( $this->hashPath ) ) {
1498
			$this->assertRepoDefined();
1499
			$this->hashPath = $this->repo->getHashPath( $this->getName() );
1500
		}
1501
1502
		return $this->hashPath;
1503
	}
1504
1505
	/**
1506
	 * Get the path of the file relative to the public zone root.
1507
	 * This function is overridden in OldLocalFile to be like getArchiveRel().
1508
	 *
1509
	 * @return string
1510
	 */
1511
	function getRel() {
1512
		return $this->getHashPath() . $this->getName();
1513
	}
1514
1515
	/**
1516
	 * Get the path of an archived file relative to the public zone root
1517
	 *
1518
	 * @param bool|string $suffix If not false, the name of an archived thumbnail file
1519
	 *
1520
	 * @return string
1521
	 */
1522 View Code Duplication
	function getArchiveRel( $suffix = false ) {
1523
		$path = 'archive/' . $this->getHashPath();
1524
		if ( $suffix === false ) {
1525
			$path = substr( $path, 0, -1 );
1526
		} else {
1527
			$path .= $suffix;
1528
		}
1529
1530
		return $path;
1531
	}
1532
1533
	/**
1534
	 * Get the path, relative to the thumbnail zone root, of the
1535
	 * thumbnail directory or a particular file if $suffix is specified
1536
	 *
1537
	 * @param bool|string $suffix If not false, the name of a thumbnail file
1538
	 * @return string
1539
	 */
1540
	function getThumbRel( $suffix = false ) {
1541
		$path = $this->getRel();
1542
		if ( $suffix !== false ) {
1543
			$path .= '/' . $suffix;
1544
		}
1545
1546
		return $path;
1547
	}
1548
1549
	/**
1550
	 * Get urlencoded path of the file relative to the public zone root.
1551
	 * This function is overridden in OldLocalFile to be like getArchiveUrl().
1552
	 *
1553
	 * @return string
1554
	 */
1555
	function getUrlRel() {
1556
		return $this->getHashPath() . rawurlencode( $this->getName() );
1557
	}
1558
1559
	/**
1560
	 * Get the path, relative to the thumbnail zone root, for an archived file's thumbs directory
1561
	 * or a specific thumb if the $suffix is given.
1562
	 *
1563
	 * @param string $archiveName The timestamped name of an archived image
1564
	 * @param bool|string $suffix If not false, the name of a thumbnail file
1565
	 * @return string
1566
	 */
1567 View Code Duplication
	function getArchiveThumbRel( $archiveName, $suffix = false ) {
1568
		$path = 'archive/' . $this->getHashPath() . $archiveName . "/";
1569
		if ( $suffix === false ) {
1570
			$path = substr( $path, 0, -1 );
1571
		} else {
1572
			$path .= $suffix;
1573
		}
1574
1575
		return $path;
1576
	}
1577
1578
	/**
1579
	 * Get the path of the archived file.
1580
	 *
1581
	 * @param bool|string $suffix If not false, the name of an archived file.
1582
	 * @return string
1583
	 */
1584
	function getArchivePath( $suffix = false ) {
1585
		$this->assertRepoDefined();
1586
1587
		return $this->repo->getZonePath( 'public' ) . '/' . $this->getArchiveRel( $suffix );
1588
	}
1589
1590
	/**
1591
	 * Get the path of an archived file's thumbs, or a particular thumb if $suffix is specified
1592
	 *
1593
	 * @param string $archiveName The timestamped name of an archived image
1594
	 * @param bool|string $suffix If not false, the name of a thumbnail file
1595
	 * @return string
1596
	 */
1597
	function getArchiveThumbPath( $archiveName, $suffix = false ) {
1598
		$this->assertRepoDefined();
1599
1600
		return $this->repo->getZonePath( 'thumb' ) . '/' .
1601
		$this->getArchiveThumbRel( $archiveName, $suffix );
1602
	}
1603
1604
	/**
1605
	 * Get the path of the thumbnail directory, or a particular file if $suffix is specified
1606
	 *
1607
	 * @param bool|string $suffix If not false, the name of a thumbnail file
1608
	 * @return string
1609
	 */
1610
	function getThumbPath( $suffix = false ) {
1611
		$this->assertRepoDefined();
1612
1613
		return $this->repo->getZonePath( 'thumb' ) . '/' . $this->getThumbRel( $suffix );
1614
	}
1615
1616
	/**
1617
	 * Get the path of the transcoded directory, or a particular file if $suffix is specified
1618
	 *
1619
	 * @param bool|string $suffix If not false, the name of a media file
1620
	 * @return string
1621
	 */
1622
	function getTranscodedPath( $suffix = false ) {
1623
		$this->assertRepoDefined();
1624
1625
		return $this->repo->getZonePath( 'transcoded' ) . '/' . $this->getThumbRel( $suffix );
1626
	}
1627
1628
	/**
1629
	 * Get the URL of the archive directory, or a particular file if $suffix is specified
1630
	 *
1631
	 * @param bool|string $suffix If not false, the name of an archived file
1632
	 * @return string
1633
	 */
1634 View Code Duplication
	function getArchiveUrl( $suffix = false ) {
1635
		$this->assertRepoDefined();
1636
		$ext = $this->getExtension();
1637
		$path = $this->repo->getZoneUrl( 'public', $ext ) . '/archive/' . $this->getHashPath();
1638
		if ( $suffix === false ) {
1639
			$path = substr( $path, 0, -1 );
1640
		} else {
1641
			$path .= rawurlencode( $suffix );
1642
		}
1643
1644
		return $path;
1645
	}
1646
1647
	/**
1648
	 * Get the URL of the archived file's thumbs, or a particular thumb if $suffix is specified
1649
	 *
1650
	 * @param string $archiveName The timestamped name of an archived image
1651
	 * @param bool|string $suffix If not false, the name of a thumbnail file
1652
	 * @return string
1653
	 */
1654 View Code Duplication
	function getArchiveThumbUrl( $archiveName, $suffix = false ) {
1655
		$this->assertRepoDefined();
1656
		$ext = $this->getExtension();
1657
		$path = $this->repo->getZoneUrl( 'thumb', $ext ) . '/archive/' .
1658
			$this->getHashPath() . rawurlencode( $archiveName ) . "/";
1659
		if ( $suffix === false ) {
1660
			$path = substr( $path, 0, -1 );
1661
		} else {
1662
			$path .= rawurlencode( $suffix );
1663
		}
1664
1665
		return $path;
1666
	}
1667
1668
	/**
1669
	 * Get the URL of the zone directory, or a particular file if $suffix is specified
1670
	 *
1671
	 * @param string $zone Name of requested zone
1672
	 * @param bool|string $suffix If not false, the name of a file in zone
1673
	 * @return string Path
1674
	 */
1675
	function getZoneUrl( $zone, $suffix = false ) {
1676
		$this->assertRepoDefined();
1677
		$ext = $this->getExtension();
1678
		$path = $this->repo->getZoneUrl( $zone, $ext ) . '/' . $this->getUrlRel();
1679
		if ( $suffix !== false ) {
1680
			$path .= '/' . rawurlencode( $suffix );
1681
		}
1682
1683
		return $path;
1684
	}
1685
1686
	/**
1687
	 * Get the URL of the thumbnail directory, or a particular file if $suffix is specified
1688
	 *
1689
	 * @param bool|string $suffix If not false, the name of a thumbnail file
1690
	 * @return string Path
1691
	 */
1692
	function getThumbUrl( $suffix = false ) {
1693
		return $this->getZoneUrl( 'thumb', $suffix );
1694
	}
1695
1696
	/**
1697
	 * Get the URL of the transcoded directory, or a particular file if $suffix is specified
1698
	 *
1699
	 * @param bool|string $suffix If not false, the name of a media file
1700
	 * @return string Path
1701
	 */
1702
	function getTranscodedUrl( $suffix = false ) {
1703
		return $this->getZoneUrl( 'transcoded', $suffix );
1704
	}
1705
1706
	/**
1707
	 * Get the public zone virtual URL for a current version source file
1708
	 *
1709
	 * @param bool|string $suffix If not false, the name of a thumbnail file
1710
	 * @return string
1711
	 */
1712 View Code Duplication
	function getVirtualUrl( $suffix = false ) {
1713
		$this->assertRepoDefined();
1714
		$path = $this->repo->getVirtualUrl() . '/public/' . $this->getUrlRel();
1715
		if ( $suffix !== false ) {
1716
			$path .= '/' . rawurlencode( $suffix );
1717
		}
1718
1719
		return $path;
1720
	}
1721
1722
	/**
1723
	 * Get the public zone virtual URL for an archived version source file
1724
	 *
1725
	 * @param bool|string $suffix If not false, the name of a thumbnail file
1726
	 * @return string
1727
	 */
1728 View Code Duplication
	function getArchiveVirtualUrl( $suffix = false ) {
1729
		$this->assertRepoDefined();
1730
		$path = $this->repo->getVirtualUrl() . '/public/archive/' . $this->getHashPath();
1731
		if ( $suffix === false ) {
1732
			$path = substr( $path, 0, -1 );
1733
		} else {
1734
			$path .= rawurlencode( $suffix );
1735
		}
1736
1737
		return $path;
1738
	}
1739
1740
	/**
1741
	 * Get the virtual URL for a thumbnail file or directory
1742
	 *
1743
	 * @param bool|string $suffix If not false, the name of a thumbnail file
1744
	 * @return string
1745
	 */
1746 View Code Duplication
	function getThumbVirtualUrl( $suffix = false ) {
1747
		$this->assertRepoDefined();
1748
		$path = $this->repo->getVirtualUrl() . '/thumb/' . $this->getUrlRel();
1749
		if ( $suffix !== false ) {
1750
			$path .= '/' . rawurlencode( $suffix );
1751
		}
1752
1753
		return $path;
1754
	}
1755
1756
	/**
1757
	 * @return bool
1758
	 */
1759
	function isHashed() {
1760
		$this->assertRepoDefined();
1761
1762
		return (bool)$this->repo->getHashLevels();
1763
	}
1764
1765
	/**
1766
	 * @throws MWException
1767
	 */
1768
	function readOnlyError() {
1769
		throw new MWException( get_class( $this ) . ': write operations are not supported' );
1770
	}
1771
1772
	/**
1773
	 * Record a file upload in the upload log and the image table
1774
	 * STUB
1775
	 * Overridden by LocalFile
1776
	 * @param string $oldver
1777
	 * @param string $desc
1778
	 * @param string $license
1779
	 * @param string $copyStatus
1780
	 * @param string $source
1781
	 * @param bool $watch
1782
	 * @param string|bool $timestamp
1783
	 * @param null|User $user User object or null to use $wgUser
1784
	 * @return bool
1785
	 * @throws MWException
1786
	 */
1787
	function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '',
1788
		$watch = false, $timestamp = false, User $user = null
1789
	) {
1790
		$this->readOnlyError();
1791
	}
1792
1793
	/**
1794
	 * Move or copy a file to its public location. If a file exists at the
1795
	 * destination, move it to an archive. Returns a FileRepoStatus object with
1796
	 * the archive name in the "value" member on success.
1797
	 *
1798
	 * The archive name should be passed through to recordUpload for database
1799
	 * registration.
1800
	 *
1801
	 * Options to $options include:
1802
	 *   - headers : name/value map of HTTP headers to use in response to GET/HEAD requests
1803
	 *
1804
	 * @param string|FSFile $src Local filesystem path to the source image
1805
	 * @param int $flags A bitwise combination of:
1806
	 *   File::DELETE_SOURCE    Delete the source file, i.e. move rather than copy
1807
	 * @param array $options Optional additional parameters
1808
	 * @return Status On success, the value member contains the
1809
	 *   archive name, or an empty string if it was a new file.
1810
	 *
1811
	 * STUB
1812
	 * Overridden by LocalFile
1813
	 */
1814
	function publish( $src, $flags = 0, array $options = [] ) {
1815
		$this->readOnlyError();
1816
	}
1817
1818
	/**
1819
	 * @param bool|IContextSource $context Context to use (optional)
1820
	 * @return bool
1821
	 */
1822
	function formatMetadata( $context = false ) {
1823
		if ( !$this->getHandler() ) {
1824
			return false;
1825
		}
1826
1827
		return $this->getHandler()->formatMetadata( $this, $context );
1828
	}
1829
1830
	/**
1831
	 * Returns true if the file comes from the local file repository.
1832
	 *
1833
	 * @return bool
1834
	 */
1835
	function isLocal() {
1836
		return $this->repo && $this->repo->isLocal();
1837
	}
1838
1839
	/**
1840
	 * Returns the name of the repository.
1841
	 *
1842
	 * @return string
1843
	 */
1844
	function getRepoName() {
1845
		return $this->repo ? $this->repo->getName() : 'unknown';
1846
	}
1847
1848
	/**
1849
	 * Returns the repository
1850
	 *
1851
	 * @return FileRepo|LocalRepo|bool
1852
	 */
1853
	function getRepo() {
1854
		return $this->repo;
1855
	}
1856
1857
	/**
1858
	 * Returns true if the image is an old version
1859
	 * STUB
1860
	 *
1861
	 * @return bool
1862
	 */
1863
	function isOld() {
1864
		return false;
1865
	}
1866
1867
	/**
1868
	 * Is this file a "deleted" file in a private archive?
1869
	 * STUB
1870
	 *
1871
	 * @param int $field One of DELETED_* bitfield constants
1872
	 * @return bool
1873
	 */
1874
	function isDeleted( $field ) {
1875
		return false;
1876
	}
1877
1878
	/**
1879
	 * Return the deletion bitfield
1880
	 * STUB
1881
	 * @return int
1882
	 */
1883
	function getVisibility() {
1884
		return 0;
1885
	}
1886
1887
	/**
1888
	 * Was this file ever deleted from the wiki?
1889
	 *
1890
	 * @return bool
1891
	 */
1892
	function wasDeleted() {
1893
		$title = $this->getTitle();
1894
1895
		return $title && $title->isDeletedQuick();
1896
	}
1897
1898
	/**
1899
	 * Move file to the new title
1900
	 *
1901
	 * Move current, old version and all thumbnails
1902
	 * to the new filename. Old file is deleted.
1903
	 *
1904
	 * Cache purging is done; checks for validity
1905
	 * and logging are caller's responsibility
1906
	 *
1907
	 * @param Title $target New file name
1908
	 * @return Status
1909
	 */
1910
	function move( $target ) {
1911
		$this->readOnlyError();
1912
	}
1913
1914
	/**
1915
	 * Delete all versions of the file.
1916
	 *
1917
	 * Moves the files into an archive directory (or deletes them)
1918
	 * and removes the database rows.
1919
	 *
1920
	 * Cache purging is done; logging is caller's responsibility.
1921
	 *
1922
	 * @param string $reason
1923
	 * @param bool $suppress Hide content from sysops?
1924
	 * @param User|null $user
1925
	 * @return Status
1926
	 * STUB
1927
	 * Overridden by LocalFile
1928
	 */
1929
	function delete( $reason, $suppress = false, $user = null ) {
1930
		$this->readOnlyError();
1931
	}
1932
1933
	/**
1934
	 * Restore all or specified deleted revisions to the given file.
1935
	 * Permissions and logging are left to the caller.
1936
	 *
1937
	 * May throw database exceptions on error.
1938
	 *
1939
	 * @param array $versions Set of record ids of deleted items to restore,
1940
	 *   or empty to restore all revisions.
1941
	 * @param bool $unsuppress Remove restrictions on content upon restoration?
1942
	 * @return int|bool The number of file revisions restored if successful,
1943
	 *   or false on failure
1944
	 * STUB
1945
	 * Overridden by LocalFile
1946
	 */
1947
	function restore( $versions = [], $unsuppress = false ) {
1948
		$this->readOnlyError();
1949
	}
1950
1951
	/**
1952
	 * Returns 'true' if this file is a type which supports multiple pages,
1953
	 * e.g. DJVU or PDF. Note that this may be true even if the file in
1954
	 * question only has a single page.
1955
	 *
1956
	 * @return bool
1957
	 */
1958
	function isMultipage() {
1959
		return $this->getHandler() && $this->handler->isMultiPage( $this );
1960
	}
1961
1962
	/**
1963
	 * Returns the number of pages of a multipage document, or false for
1964
	 * documents which aren't multipage documents
1965
	 *
1966
	 * @return bool|int
1967
	 */
1968 View Code Duplication
	function pageCount() {
1969
		if ( !isset( $this->pageCount ) ) {
1970
			if ( $this->getHandler() && $this->handler->isMultiPage( $this ) ) {
1971
				$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...
1972
			} else {
1973
				$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...
1974
			}
1975
		}
1976
1977
		return $this->pageCount;
1978
	}
1979
1980
	/**
1981
	 * Calculate the height of a thumbnail using the source and destination width
1982
	 *
1983
	 * @param int $srcWidth
1984
	 * @param int $srcHeight
1985
	 * @param int $dstWidth
1986
	 *
1987
	 * @return int
1988
	 */
1989
	static function scaleHeight( $srcWidth, $srcHeight, $dstWidth ) {
1990
		// Exact integer multiply followed by division
1991
		if ( $srcWidth == 0 ) {
1992
			return 0;
1993
		} else {
1994
			return round( $srcHeight * $dstWidth / $srcWidth );
1995
		}
1996
	}
1997
1998
	/**
1999
	 * Get an image size array like that returned by getImageSize(), or false if it
2000
	 * can't be determined. Loads the image size directly from the file ignoring caches.
2001
	 *
2002
	 * @note Use getWidth()/getHeight() instead of this method unless you have a
2003
	 *  a good reason. This method skips all caches.
2004
	 *
2005
	 * @param string $filePath The path to the file (e.g. From getLocalPathRef() )
2006
	 * @return array The width, followed by height, with optionally more things after
2007
	 */
2008
	function getImageSize( $filePath ) {
2009
		if ( !$this->getHandler() ) {
2010
			return false;
2011
		}
2012
2013
		return $this->getHandler()->getImageSize( $this, $filePath );
2014
	}
2015
2016
	/**
2017
	 * Get the URL of the image description page. May return false if it is
2018
	 * unknown or not applicable.
2019
	 *
2020
	 * @return string
2021
	 */
2022
	function getDescriptionUrl() {
2023
		if ( $this->repo ) {
2024
			return $this->repo->getDescriptionUrl( $this->getName() );
2025
		} else {
2026
			return false;
2027
		}
2028
	}
2029
2030
	/**
2031
	 * Get the HTML text of the description page, if available
2032
	 *
2033
	 * @param bool|Language $lang Optional language to fetch description in
2034
	 * @return string
2035
	 */
2036
	function getDescriptionText( $lang = false ) {
2037
		global $wgLang;
2038
2039
		if ( !$this->repo || !$this->repo->fetchDescription ) {
2040
			return false;
2041
		}
2042
2043
		$lang = $lang ?: $wgLang;
2044
2045
		$renderUrl = $this->repo->getDescriptionRenderUrl( $this->getName(), $lang->getCode() );
2046
		if ( $renderUrl ) {
2047
			$cache = ObjectCache::getMainWANInstance();
2048
			$key = $this->repo->getLocalCacheKey(
2049
				'RemoteFileDescription',
2050
				'url',
2051
				$lang->getCode(),
2052
				$this->getName()
2053
			);
2054
2055
			return $cache->getWithSetCallback(
2056
				$key,
2057
				$this->repo->descriptionCacheExpiry ?: $cache::TTL_UNCACHEABLE,
2058 View Code Duplication
				function ( $oldValue, &$ttl, array &$setOpts ) use ( $renderUrl ) {
2059
					wfDebug( "Fetching shared description from $renderUrl\n" );
2060
					$res = Http::get( $renderUrl, [], __METHOD__ );
2061
					if ( !$res ) {
2062
						$ttl = WANObjectCache::TTL_UNCACHEABLE;
2063
					}
2064
2065
					return $res;
2066
				}
2067
			);
2068
		}
2069
2070
		return false;
2071
	}
2072
2073
	/**
2074
	 * Get description of file revision
2075
	 * STUB
2076
	 *
2077
	 * @param int $audience One of:
2078
	 *   File::FOR_PUBLIC       to be displayed to all users
2079
	 *   File::FOR_THIS_USER    to be displayed to the given user
2080
	 *   File::RAW              get the description regardless of permissions
2081
	 * @param User $user User object to check for, only if FOR_THIS_USER is
2082
	 *   passed to the $audience parameter
2083
	 * @return string
2084
	 */
2085
	function getDescription( $audience = self::FOR_PUBLIC, User $user = null ) {
2086
		return null;
2087
	}
2088
2089
	/**
2090
	 * Get the 14-character timestamp of the file upload
2091
	 *
2092
	 * @return string|bool TS_MW timestamp or false on failure
2093
	 */
2094
	function getTimestamp() {
2095
		$this->assertRepoDefined();
2096
2097
		return $this->repo->getFileTimestamp( $this->getPath() );
0 ignored issues
show
It seems like $this->getPath() targeting File::getPath() can also be of type boolean; however, FileRepo::getFileTimestamp() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2098
	}
2099
2100
	/**
2101
	 * Returns the timestamp (in TS_MW format) of the last change of the description page.
2102
	 * Returns false if the file does not have a description page, or retrieving the timestamp
2103
	 * would be expensive.
2104
	 * @since 1.25
2105
	 * @return string|bool
2106
	 */
2107
	public function getDescriptionTouched() {
2108
		return false;
2109
	}
2110
2111
	/**
2112
	 * Get the SHA-1 base 36 hash of the file
2113
	 *
2114
	 * @return string
2115
	 */
2116
	function getSha1() {
2117
		$this->assertRepoDefined();
2118
2119
		return $this->repo->getFileSha1( $this->getPath() );
0 ignored issues
show
It seems like $this->getPath() targeting File::getPath() can also be of type boolean; however, FileRepo::getFileSha1() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2120
	}
2121
2122
	/**
2123
	 * Get the deletion archive key, "<sha1>.<ext>"
2124
	 *
2125
	 * @return string
2126
	 */
2127
	function getStorageKey() {
2128
		$hash = $this->getSha1();
2129
		if ( !$hash ) {
2130
			return false;
2131
		}
2132
		$ext = $this->getExtension();
2133
		$dotExt = $ext === '' ? '' : ".$ext";
2134
2135
		return $hash . $dotExt;
2136
	}
2137
2138
	/**
2139
	 * Determine if the current user is allowed to view a particular
2140
	 * field of this file, if it's marked as deleted.
2141
	 * STUB
2142
	 * @param int $field
2143
	 * @param User $user User object to check, or null to use $wgUser
2144
	 * @return bool
2145
	 */
2146
	function userCan( $field, User $user = null ) {
2147
		return true;
2148
	}
2149
2150
	/**
2151
	 * @return array HTTP header name/value map to use for HEAD/GET request responses
2152
	 */
2153
	function getStreamHeaders() {
2154
		$handler = $this->getHandler();
2155
		if ( $handler ) {
2156
			return $handler->getStreamHeaders( $this->getMetadata() );
2157
		} else {
2158
			return [];
2159
		}
2160
	}
2161
2162
	/**
2163
	 * @return string
2164
	 */
2165
	function getLongDesc() {
2166
		$handler = $this->getHandler();
2167
		if ( $handler ) {
2168
			return $handler->getLongDesc( $this );
2169
		} else {
2170
			return MediaHandler::getGeneralLongDesc( $this );
2171
		}
2172
	}
2173
2174
	/**
2175
	 * @return string
2176
	 */
2177
	function getShortDesc() {
2178
		$handler = $this->getHandler();
2179
		if ( $handler ) {
2180
			return $handler->getShortDesc( $this );
2181
		} else {
2182
			return MediaHandler::getGeneralShortDesc( $this );
2183
		}
2184
	}
2185
2186
	/**
2187
	 * @return string
2188
	 */
2189
	function getDimensionsString() {
2190
		$handler = $this->getHandler();
2191
		if ( $handler ) {
2192
			return $handler->getDimensionsString( $this );
2193
		} else {
2194
			return '';
2195
		}
2196
	}
2197
2198
	/**
2199
	 * @return string
2200
	 */
2201
	function getRedirected() {
2202
		return $this->redirected;
2203
	}
2204
2205
	/**
2206
	 * @return Title|null
2207
	 */
2208
	function getRedirectedTitle() {
2209
		if ( $this->redirected ) {
2210
			if ( !$this->redirectTitle ) {
2211
				$this->redirectTitle = Title::makeTitle( NS_FILE, $this->redirected );
2212
			}
2213
2214
			return $this->redirectTitle;
2215
		}
2216
2217
		return null;
2218
	}
2219
2220
	/**
2221
	 * @param string $from
2222
	 * @return void
2223
	 */
2224
	function redirectedFrom( $from ) {
2225
		$this->redirected = $from;
2226
	}
2227
2228
	/**
2229
	 * @return bool
2230
	 */
2231
	function isMissing() {
2232
		return false;
2233
	}
2234
2235
	/**
2236
	 * Check if this file object is small and can be cached
2237
	 * @return bool
2238
	 */
2239
	public function isCacheable() {
2240
		return true;
2241
	}
2242
2243
	/**
2244
	 * Assert that $this->repo is set to a valid FileRepo instance
2245
	 * @throws MWException
2246
	 */
2247
	protected function assertRepoDefined() {
2248
		if ( !( $this->repo instanceof $this->repoClass ) ) {
2249
			throw new MWException( "A {$this->repoClass} object is not set for this File.\n" );
2250
		}
2251
	}
2252
2253
	/**
2254
	 * Assert that $this->title is set to a Title
2255
	 * @throws MWException
2256
	 */
2257
	protected function assertTitleDefined() {
2258
		if ( !( $this->title instanceof Title ) ) {
2259
			throw new MWException( "A Title object is not set for this File.\n" );
2260
		}
2261
	}
2262
2263
	/**
2264
	 * True if creating thumbnails from the file is large or otherwise resource-intensive.
2265
	 * @return bool
2266
	 */
2267
	public function isExpensiveToThumbnail() {
2268
		$handler = $this->getHandler();
2269
		return $handler ? $handler->isExpensiveToThumbnail( $this ) : false;
2270
	}
2271
2272
	/**
2273
	 * Whether the thumbnails created on the same server as this code is running.
2274
	 * @since 1.25
2275
	 * @return bool
2276
	 */
2277
	public function isTransformedLocally() {
2278
		return true;
2279
	}
2280
}
2281