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/media/TransformationalImageHandler.php (1 issue)

Severity

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
 * Base class for handlers which require transforming images in a
4
 * similar way as BitmapHandler does.
5
 *
6
 * This was split from BitmapHandler on the basis that some extensions
7
 * might want to work in a similar way to BitmapHandler, but for
8
 * different formats.
9
 *
10
 * This program is free software; you can redistribute it and/or modify
11
 * it under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation; either version 2 of the License, or
13
 * (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License along
21
 * with this program; if not, write to the Free Software Foundation, Inc.,
22
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23
 * http://www.gnu.org/copyleft/gpl.html
24
 *
25
 * @file
26
 * @ingroup Media
27
 */
28
use MediaWiki\MediaWikiServices;
29
30
/**
31
 * Handler for images that need to be transformed
32
 *
33
 * @since 1.24
34
 * @ingroup Media
35
 */
36
abstract class TransformationalImageHandler extends ImageHandler {
37
	/**
38
	 * @param File $image
39
	 * @param array $params Transform parameters. Entries with the keys 'width'
40
	 * and 'height' are the respective screen width and height, while the keys
41
	 * 'physicalWidth' and 'physicalHeight' indicate the thumbnail dimensions.
42
	 * @return bool
43
	 */
44
	function normaliseParams( $image, &$params ) {
45
		if ( !parent::normaliseParams( $image, $params ) ) {
46
			return false;
47
		}
48
49
		# Obtain the source, pre-rotation dimensions
50
		$srcWidth = $image->getWidth( $params['page'] );
51
		$srcHeight = $image->getHeight( $params['page'] );
52
53
		# Don't make an image bigger than the source
54
		if ( $params['physicalWidth'] >= $srcWidth ) {
55
			$params['physicalWidth'] = $srcWidth;
56
			$params['physicalHeight'] = $srcHeight;
57
58
			# Skip scaling limit checks if no scaling is required
59
			# due to requested size being bigger than source.
60
			if ( !$image->mustRender() ) {
61
				return true;
62
			}
63
		}
64
65
		return true;
66
	}
67
68
	/**
69
	 * Extracts the width/height if the image will be scaled before rotating
70
	 *
71
	 * This will match the physical size/aspect ratio of the original image
72
	 * prior to application of the rotation -- so for a portrait image that's
73
	 * stored as raw landscape with 90-degress rotation, the resulting size
74
	 * will be wider than it is tall.
75
	 *
76
	 * @param array $params Parameters as returned by normaliseParams
77
	 * @param int $rotation The rotation angle that will be applied
78
	 * @return array ($width, $height) array
79
	 */
80
	public function extractPreRotationDimensions( $params, $rotation ) {
81
		if ( $rotation == 90 || $rotation == 270 ) {
82
			# We'll resize before rotation, so swap the dimensions again
83
			$width = $params['physicalHeight'];
84
			$height = $params['physicalWidth'];
85
		} else {
86
			$width = $params['physicalWidth'];
87
			$height = $params['physicalHeight'];
88
		}
89
90
		return [ $width, $height ];
91
	}
92
93
	/**
94
	 * Create a thumbnail.
95
	 *
96
	 * This sets up various parameters, and then calls a helper method
97
	 * based on $this->getScalerType in order to scale the image.
98
	 *
99
	 * @param File $image
100
	 * @param string $dstPath
101
	 * @param string $dstUrl
102
	 * @param array $params
103
	 * @param int $flags
104
	 * @return MediaTransformError|ThumbnailImage|TransformParameterError
105
	 */
106
	function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
107
		if ( !$this->normaliseParams( $image, $params ) ) {
108
			return new TransformParameterError( $params );
109
		}
110
111
		# Create a parameter array to pass to the scaler
112
		$scalerParams = [
113
			# The size to which the image will be resized
114
			'physicalWidth' => $params['physicalWidth'],
115
			'physicalHeight' => $params['physicalHeight'],
116
			'physicalDimensions' => "{$params['physicalWidth']}x{$params['physicalHeight']}",
117
			# The size of the image on the page
118
			'clientWidth' => $params['width'],
119
			'clientHeight' => $params['height'],
120
			# Comment as will be added to the Exif of the thumbnail
121
			'comment' => isset( $params['descriptionUrl'] )
122
				? "File source: {$params['descriptionUrl']}"
123
				: '',
124
			# Properties of the original image
125
			'srcWidth' => $image->getWidth(),
126
			'srcHeight' => $image->getHeight(),
127
			'mimeType' => $image->getMimeType(),
128
			'dstPath' => $dstPath,
129
			'dstUrl' => $dstUrl,
130
			'interlace' => isset( $params['interlace'] ) ? $params['interlace'] : false,
131
		];
132
133
		if ( isset( $params['quality'] ) && $params['quality'] === 'low' ) {
134
			$scalerParams['quality'] = 30;
135
		}
136
137
		// For subclasses that might be paged.
138
		if ( $image->isMultipage() && isset( $params['page'] ) ) {
139
			$scalerParams['page'] = intval( $params['page'] );
140
		}
141
142
		# Determine scaler type
143
		$scaler = $this->getScalerType( $dstPath );
144
145
		if ( is_array( $scaler ) ) {
146
			$scalerName = get_class( $scaler[0] );
147
		} else {
148
			$scalerName = $scaler;
149
		}
150
151
		wfDebug( __METHOD__ . ": creating {$scalerParams['physicalDimensions']} " .
152
			"thumbnail at $dstPath using scaler $scalerName\n" );
153
154
		if ( !$image->mustRender() &&
155
			$scalerParams['physicalWidth'] == $scalerParams['srcWidth']
156
			&& $scalerParams['physicalHeight'] == $scalerParams['srcHeight']
157
			&& !isset( $scalerParams['quality'] )
158
		) {
159
160
			# normaliseParams (or the user) wants us to return the unscaled image
161
			wfDebug( __METHOD__ . ": returning unscaled image\n" );
162
163
			return $this->getClientScalingThumbnailImage( $image, $scalerParams );
164
		}
165
166
		if ( $scaler == 'client' ) {
167
			# Client-side image scaling, use the source URL
168
			# Using the destination URL in a TRANSFORM_LATER request would be incorrect
169
			return $this->getClientScalingThumbnailImage( $image, $scalerParams );
170
		}
171
172
		if ( $image->isTransformedLocally() && !$this->isImageAreaOkForThumbnaling( $image, $params ) ) {
173
			global $wgMaxImageArea;
174
			return new TransformTooBigImageAreaError( $params, $wgMaxImageArea );
175
		}
176
177
		if ( $flags & self::TRANSFORM_LATER ) {
178
			wfDebug( __METHOD__ . ": Transforming later per flags.\n" );
179
			$newParams = [
180
				'width' => $scalerParams['clientWidth'],
181
				'height' => $scalerParams['clientHeight']
182
			];
183
			if ( isset( $params['quality'] ) ) {
184
				$newParams['quality'] = $params['quality'];
185
			}
186 View Code Duplication
			if ( isset( $params['page'] ) && $params['page'] ) {
187
				$newParams['page'] = $params['page'];
188
			}
189
			return new ThumbnailImage( $image, $dstUrl, false, $newParams );
190
		}
191
192
		# Try to make a target path for the thumbnail
193
		if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) {
194
			wfDebug( __METHOD__ . ": Unable to create thumbnail destination " .
195
				"directory, falling back to client scaling\n" );
196
197
			return $this->getClientScalingThumbnailImage( $image, $scalerParams );
198
		}
199
200
		# Transform functions and binaries need a FS source file
201
		$thumbnailSource = $this->getThumbnailSource( $image, $params );
202
203
		// If the source isn't the original, disable EXIF rotation because it's already been applied
204
		if ( $scalerParams['srcWidth'] != $thumbnailSource['width']
205
			|| $scalerParams['srcHeight'] != $thumbnailSource['height'] ) {
206
			$scalerParams['disableRotation'] = true;
207
		}
208
209
		$scalerParams['srcPath'] = $thumbnailSource['path'];
210
		$scalerParams['srcWidth'] = $thumbnailSource['width'];
211
		$scalerParams['srcHeight'] = $thumbnailSource['height'];
212
213 View Code Duplication
		if ( $scalerParams['srcPath'] === false ) { // Failed to get local copy
214
			wfDebugLog( 'thumbnail',
215
				sprintf( 'Thumbnail failed on %s: could not get local copy of "%s"',
216
					wfHostname(), $image->getName() ) );
217
218
			return new MediaTransformError( 'thumbnail_error',
219
				$scalerParams['clientWidth'], $scalerParams['clientHeight'],
220
				wfMessage( 'filemissing' )->text()
221
			);
222
		}
223
224
		# Try a hook. Called "Bitmap" for historical reasons.
225
		/** @var $mto MediaTransformOutput */
226
		$mto = null;
227
		Hooks::run( 'BitmapHandlerTransform', [ $this, $image, &$scalerParams, &$mto ] );
228
		if ( !is_null( $mto ) ) {
229
			wfDebug( __METHOD__ . ": Hook to BitmapHandlerTransform created an mto\n" );
230
			$scaler = 'hookaborted';
231
		}
232
233
		// $scaler will return a MediaTransformError on failure, or false on success.
234
		// If the scaler is succesful, it will have created a thumbnail at the destination
235
		// path.
236
		if ( is_array( $scaler ) && is_callable( $scaler ) ) {
237
			// Allow subclasses to specify their own rendering methods.
238
			$err = call_user_func( $scaler, $image, $scalerParams );
239
		} else {
240
			switch ( $scaler ) {
241
				case 'hookaborted':
242
					# Handled by the hook above
243
					$err = $mto->isError() ? $mto : false;
244
					break;
245
				case 'im':
246
					$err = $this->transformImageMagick( $image, $scalerParams );
247
					break;
248
				case 'custom':
249
					$err = $this->transformCustom( $image, $scalerParams );
250
					break;
251
				case 'imext':
252
					$err = $this->transformImageMagickExt( $image, $scalerParams );
253
					break;
254
				case 'gd':
255
				default:
256
					$err = $this->transformGd( $image, $scalerParams );
257
					break;
258
			}
259
		}
260
261
		# Remove the file if a zero-byte thumbnail was created, or if there was an error
262
		$removed = $this->removeBadFile( $dstPath, (bool)$err );
263
		if ( $err ) {
264
			# transform returned MediaTransforError
265
			return $err;
266
		} elseif ( $removed ) {
267
			# Thumbnail was zero-byte and had to be removed
268
			return new MediaTransformError( 'thumbnail_error',
269
				$scalerParams['clientWidth'], $scalerParams['clientHeight'],
270
				wfMessage( 'unknown-error' )->text()
271
			);
272
		} elseif ( $mto ) {
273
			return $mto;
274
		} else {
275
			$newParams = [
276
				'width' => $scalerParams['clientWidth'],
277
				'height' => $scalerParams['clientHeight']
278
			];
279
			if ( isset( $params['quality'] ) ) {
280
				$newParams['quality'] = $params['quality'];
281
			}
282 View Code Duplication
			if ( isset( $params['page'] ) && $params['page'] ) {
283
				$newParams['page'] = $params['page'];
284
			}
285
			return new ThumbnailImage( $image, $dstUrl, $dstPath, $newParams );
286
		}
287
	}
288
289
	/**
290
	 * Get the source file for the transform
291
	 *
292
	 * @param File $file
293
	 * @param array $params
294
	 * @return array Array with keys  width, height and path.
295
	 */
296
	protected function getThumbnailSource( $file, $params ) {
297
		return $file->getThumbnailSource( $params );
298
	}
299
300
	/**
301
	 * Returns what sort of scaler type should be used.
302
	 *
303
	 * Values can be one of client, im, custom, gd, imext, or an array
304
	 * of object, method-name to call that specific method.
305
	 *
306
	 * If specifying a custom scaler command with [ Obj, method ],
307
	 * the method in question should take 2 parameters, a File object,
308
	 * and a $scalerParams array with various options (See doTransform
309
	 * for what is in $scalerParams). On error it should return a
310
	 * MediaTransformError object. On success it should return false,
311
	 * and simply make sure the thumbnail file is located at
312
	 * $scalerParams['dstPath'].
313
	 *
314
	 * If there is a problem with the output path, it returns "client"
315
	 * to do client side scaling.
316
	 *
317
	 * @param string $dstPath
318
	 * @param bool $checkDstPath Check that $dstPath is valid
319
	 * @return string|Callable One of client, im, custom, gd, imext, or a Callable array.
320
	 */
321
	abstract protected function getScalerType( $dstPath, $checkDstPath = true );
322
323
	/**
324
	 * Get a ThumbnailImage that respresents an image that will be scaled
325
	 * client side
326
	 *
327
	 * @param File $image File associated with this thumbnail
328
	 * @param array $scalerParams Array with scaler params
329
	 * @return ThumbnailImage
330
	 *
331
	 * @todo FIXME: No rotation support
332
	 */
333
	protected function getClientScalingThumbnailImage( $image, $scalerParams ) {
334
		$params = [
335
			'width' => $scalerParams['clientWidth'],
336
			'height' => $scalerParams['clientHeight']
337
		];
338
339
		return new ThumbnailImage( $image, $image->getUrl(), null, $params );
340
	}
341
342
	/**
343
	 * Transform an image using ImageMagick
344
	 *
345
	 * This is a stub method. The real method is in BitmapHander.
346
	 *
347
	 * @param File $image File associated with this thumbnail
348
	 * @param array $params Array with scaler params
349
	 *
350
	 * @return MediaTransformError Error object if error occurred, false (=no error) otherwise
351
	 */
352
	protected function transformImageMagick( $image, $params ) {
353
		return $this->getMediaTransformError( $params, "Unimplemented" );
354
	}
355
356
	/**
357
	 * Transform an image using the Imagick PHP extension
358
	 *
359
	 * This is a stub method. The real method is in BitmapHander.
360
	 *
361
	 * @param File $image File associated with this thumbnail
362
	 * @param array $params Array with scaler params
363
	 *
364
	 * @return MediaTransformError Error object if error occurred, false (=no error) otherwise
365
	 */
366
	protected function transformImageMagickExt( $image, $params ) {
367
		return $this->getMediaTransformError( $params, "Unimplemented" );
368
	}
369
370
	/**
371
	 * Transform an image using a custom command
372
	 *
373
	 * This is a stub method. The real method is in BitmapHander.
374
	 *
375
	 * @param File $image File associated with this thumbnail
376
	 * @param array $params Array with scaler params
377
	 *
378
	 * @return MediaTransformError Error object if error occurred, false (=no error) otherwise
379
	 */
380
	protected function transformCustom( $image, $params ) {
381
		return $this->getMediaTransformError( $params, "Unimplemented" );
382
	}
383
384
	/**
385
	 * Get a MediaTransformError with error 'thumbnail_error'
386
	 *
387
	 * @param array $params Parameter array as passed to the transform* functions
388
	 * @param string $errMsg Error message
389
	 * @return MediaTransformError
390
	 */
391
	public function getMediaTransformError( $params, $errMsg ) {
392
		return new MediaTransformError( 'thumbnail_error', $params['clientWidth'],
393
			$params['clientHeight'], $errMsg );
394
	}
395
396
	/**
397
	 * Transform an image using the built in GD library
398
	 *
399
	 * This is a stub method. The real method is in BitmapHander.
400
	 *
401
	 * @param File $image File associated with this thumbnail
402
	 * @param array $params Array with scaler params
403
	 *
404
	 * @return MediaTransformError Error object if error occurred, false (=no error) otherwise
405
	 */
406
	protected function transformGd( $image, $params ) {
407
		return $this->getMediaTransformError( $params, "Unimplemented" );
408
	}
409
410
	/**
411
	 * Escape a string for ImageMagick's property input (e.g. -set -comment)
412
	 * See InterpretImageProperties() in magick/property.c
413
	 * @param string $s
414
	 * @return string
415
	 */
416
	function escapeMagickProperty( $s ) {
417
		// Double the backslashes
418
		$s = str_replace( '\\', '\\\\', $s );
419
		// Double the percents
420
		$s = str_replace( '%', '%%', $s );
421
		// Escape initial - or @
422
		if ( strlen( $s ) > 0 && ( $s[0] === '-' || $s[0] === '@' ) ) {
423
			$s = '\\' . $s;
424
		}
425
426
		return $s;
427
	}
428
429
	/**
430
	 * Escape a string for ImageMagick's input filenames. See ExpandFilenames()
431
	 * and GetPathComponent() in magick/utility.c.
432
	 *
433
	 * This won't work with an initial ~ or @, so input files should be prefixed
434
	 * with the directory name.
435
	 *
436
	 * Glob character unescaping is broken in ImageMagick before 6.6.1-5, but
437
	 * it's broken in a way that doesn't involve trying to convert every file
438
	 * in a directory, so we're better off escaping and waiting for the bugfix
439
	 * to filter down to users.
440
	 *
441
	 * @param string $path The file path
442
	 * @param bool|string $scene The scene specification, or false if there is none
443
	 * @throws MWException
444
	 * @return string
445
	 */
446
	function escapeMagickInput( $path, $scene = false ) {
447
		# Die on initial metacharacters (caller should prepend path)
448
		$firstChar = substr( $path, 0, 1 );
449
		if ( $firstChar === '~' || $firstChar === '@' ) {
450
			throw new MWException( __METHOD__ . ': cannot escape this path name' );
451
		}
452
453
		# Escape glob chars
454
		$path = preg_replace( '/[*?\[\]{}]/', '\\\\\0', $path );
455
456
		return $this->escapeMagickPath( $path, $scene );
457
	}
458
459
	/**
460
	 * Escape a string for ImageMagick's output filename. See
461
	 * InterpretImageFilename() in magick/image.c.
462
	 * @param string $path The file path
463
	 * @param bool|string $scene The scene specification, or false if there is none
464
	 * @return string
465
	 */
466
	function escapeMagickOutput( $path, $scene = false ) {
467
		$path = str_replace( '%', '%%', $path );
468
469
		return $this->escapeMagickPath( $path, $scene );
470
	}
471
472
	/**
473
	 * Armour a string against ImageMagick's GetPathComponent(). This is a
474
	 * helper function for escapeMagickInput() and escapeMagickOutput().
475
	 *
476
	 * @param string $path The file path
477
	 * @param bool|string $scene The scene specification, or false if there is none
478
	 * @throws MWException
479
	 * @return string
480
	 */
481
	protected function escapeMagickPath( $path, $scene = false ) {
482
		# Die on format specifiers (other than drive letters). The regex is
483
		# meant to match all the formats you get from "convert -list format"
484
		if ( preg_match( '/^([a-zA-Z0-9-]+):/', $path, $m ) ) {
485
			if ( wfIsWindows() && is_dir( $m[0] ) ) {
0 ignored issues
show
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
486
				// OK, it's a drive letter
487
				// ImageMagick has a similar exception, see IsMagickConflict()
488
			} else {
489
				throw new MWException( __METHOD__ . ': unexpected colon character in path name' );
490
			}
491
		}
492
493
		# If there are square brackets, add a do-nothing scene specification
494
		# to force a literal interpretation
495
		if ( $scene === false ) {
496
			if ( strpos( $path, '[' ) !== false ) {
497
				$path .= '[0--1]';
498
			}
499
		} else {
500
			$path .= "[$scene]";
501
		}
502
503
		return $path;
504
	}
505
506
	/**
507
	 * Retrieve the version of the installed ImageMagick
508
	 * You can use PHPs version_compare() to use this value
509
	 * Value is cached for one hour.
510
	 * @return string|bool Representing the IM version; false on error
511
	 */
512
	protected function getMagickVersion() {
513
		$cache = MediaWikiServices::getInstance()->getLocalServerObjectCache();
514
		$method = __METHOD__;
515
		return $cache->getWithSetCallback(
516
			'imagemagick-version',
517
			$cache::TTL_HOUR,
518
			function () use ( $method ) {
519
				global $wgImageMagickConvertCommand;
520
521
				$cmd = wfEscapeShellArg( $wgImageMagickConvertCommand ) . ' -version';
522
				wfDebug( $method . ": Running convert -version\n" );
523
				$retval = '';
524
				$return = wfShellExec( $cmd, $retval );
525
				$x = preg_match(
526
					'/Version: ImageMagick ([0-9]*\.[0-9]*\.[0-9]*)/', $return, $matches
527
				);
528
				if ( $x != 1 ) {
529
					wfDebug( $method . ": ImageMagick version check failed\n" );
530
					return false;
531
				}
532
533
				return $matches[1];
534
			}
535
		);
536
	}
537
538
	/**
539
	 * Returns whether the current scaler supports rotation.
540
	 *
541
	 * @since 1.24 No longer static
542
	 * @return bool
543
	 */
544
	public function canRotate() {
545
		return false;
546
	}
547
548
	/**
549
	 * Should we automatically rotate an image based on exif
550
	 *
551
	 * @since 1.24 No longer static
552
	 * @see $wgEnableAutoRotation
553
	 * @return bool Whether auto rotation is enabled
554
	 */
555
	public function autoRotateEnabled() {
556
		return false;
557
	}
558
559
	/**
560
	 * Rotate a thumbnail.
561
	 *
562
	 * This is a stub. See BitmapHandler::rotate.
563
	 *
564
	 * @param File $file
565
	 * @param array $params Rotate parameters.
566
	 *   'rotation' clockwise rotation in degrees, allowed are multiples of 90
567
	 * @since 1.24 Is non-static. From 1.21 it was static
568
	 * @return bool
569
	 */
570
	public function rotate( $file, $params ) {
571
		return new MediaTransformError( 'thumbnail_error', 0, 0,
572
			get_class( $this ) . ' rotation not implemented' );
573
	}
574
575
	/**
576
	 * Returns whether the file needs to be rendered. Returns true if the
577
	 * file requires rotation and we are able to rotate it.
578
	 *
579
	 * @param File $file
580
	 * @return bool
581
	 */
582
	public function mustRender( $file ) {
583
		return $this->canRotate() && $this->getRotation( $file ) != 0;
584
	}
585
586
	/**
587
	 * Check if the file is smaller than the maximum image area for thumbnailing.
588
	 *
589
	 * Runs the 'BitmapHandlerCheckImageArea' hook.
590
	 *
591
	 * @param File $file
592
	 * @param array $params
593
	 * @return bool
594
	 * @since 1.25
595
	 */
596
	public function isImageAreaOkForThumbnaling( $file, &$params ) {
597
		global $wgMaxImageArea;
598
599
		# For historical reasons, hook starts with BitmapHandler
600
		$checkImageAreaHookResult = null;
601
		Hooks::run(
602
			'BitmapHandlerCheckImageArea',
603
			[ $file, &$params, &$checkImageAreaHookResult ]
604
		);
605
606
		if ( !is_null( $checkImageAreaHookResult ) ) {
607
			// was set by hook, so return that value
608
			return (bool)$checkImageAreaHookResult;
609
		}
610
611
		$srcWidth = $file->getWidth( $params['page'] );
612
		$srcHeight = $file->getHeight( $params['page'] );
613
614
		if ( $srcWidth * $srcHeight > $wgMaxImageArea
615
			&& !( $file->getMimeType() == 'image/jpeg'
616
				&& $this->getScalerType( false, false ) == 'im' )
617
		) {
618
			# Only ImageMagick can efficiently downsize jpg images without loading
619
			# the entire file in memory
620
			return false;
621
		}
622
		return true;
623
	}
624
}
625