Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
15.35% covered (danger)
15.35%
31 / 202
0.00% covered (danger)
0.00%
0 / 20
CRAP
0.00% covered (danger)
0.00%
0 / 1
TransformationalImageHandler
15.42% covered (danger)
15.42%
31 / 201
0.00% covered (danger)
0.00%
0 / 20
3952.03
0.00% covered (danger)
0.00%
0 / 1
 normaliseParams
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
 extractPreRotationDimensions
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 doTransform
27.93% covered (danger)
27.93%
31 / 111
0.00% covered (danger)
0.00%
0 / 1
608.42
 getThumbnailSource
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getScalerType
n/a
0 / 0
n/a
0 / 0
0
 getClientScalingThumbnailImage
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 transformImageMagick
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 transformImageMagickExt
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 transformCustom
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMediaTransformError
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 transformGd
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 escapeMagickProperty
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
 escapeMagickInput
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 escapeMagickOutput
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 escapeMagickPath
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
42
 getMagickVersion
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
6
 canRotate
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 autoRotateEnabled
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 rotate
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 mustRender
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 isImageAreaOkForThumbnaling
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
42
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 * @license GPL-2.0-or-later
11 * @file
12 * @ingroup Media
13 */
14
15namespace MediaWiki\Media;
16
17use InvalidArgumentException;
18use MediaWiki\FileRepo\File\File;
19use MediaWiki\HookContainer\HookRunner;
20use MediaWiki\MainConfigNames;
21use MediaWiki\MediaWikiServices;
22use MediaWiki\Shell\Shell;
23
24/**
25 * Handler for images that need to be transformed
26 *
27 * @stable to extend
28 *
29 * @since 1.24
30 * @ingroup Media
31 */
32abstract class TransformationalImageHandler extends ImageHandler {
33    /**
34     * @stable to override
35     * @param File $image
36     * @param array &$params Transform parameters. Entries with the keys 'width'
37     * and 'height' are the respective screen width and height, while the keys
38     * 'physicalWidth' and 'physicalHeight' indicate the thumbnail dimensions.
39     * @return bool
40     */
41    public function normaliseParams( $image, &$params ) {
42        if ( !parent::normaliseParams( $image, $params ) ) {
43            return false;
44        }
45
46        # Obtain the source, pre-rotation dimensions
47        $srcWidth = $image->getWidth( $params['page'] );
48        $srcHeight = $image->getHeight( $params['page'] );
49
50        $params['physicalWidth'] = $this->getSteppedThumbWidth(
51            $image, $params['physicalWidth'], $srcWidth, $srcHeight
52        );
53        $params['physicalHeight'] = File::scaleHeight( $srcWidth, $srcHeight, $params['physicalWidth'] );
54
55        # Don't make an image bigger than the source
56        if ( $params['physicalWidth'] >= $srcWidth ) {
57            $params['physicalWidth'] = $srcWidth;
58            $params['physicalHeight'] = $srcHeight;
59
60            # Skip scaling limit checks if no scaling is required
61            # due to requested size being bigger than source.
62            if ( !$image->mustRender() ) {
63                return true;
64            }
65        }
66
67        return true;
68    }
69
70    /**
71     * Extracts the width/height if the image will be scaled before rotating
72     *
73     * This will match the physical size/aspect ratio of the original image
74     * prior to application of the rotation -- so for a portrait image that's
75     * stored as raw landscape with 90-degrees rotation, the resulting size
76     * will be wider than it is tall.
77     *
78     * @param array $params Parameters as returned by normaliseParams
79     * @param int $rotation The rotation angle that will be applied
80     * @return array ($width, $height) array
81     */
82    public function extractPreRotationDimensions( $params, $rotation ) {
83        if ( $rotation === 90 || $rotation === 270 ) {
84            // We'll resize before rotation, so swap the dimensions again
85            $width = $params['physicalHeight'];
86            $height = $params['physicalWidth'];
87        } else {
88            $width = $params['physicalWidth'];
89            $height = $params['physicalHeight'];
90        }
91
92        return [ $width, $height ];
93    }
94
95    /**
96     * Create a thumbnail.
97     *
98     * This sets up various parameters, and then calls a helper method
99     * based on $this->getScalerType in order to scale the image.
100     * @stable to override
101     *
102     * @param File $image
103     * @param string $dstPath
104     * @param string $dstUrl
105     * @param array $params
106     * @param int $flags
107     * @return MediaTransformError|ThumbnailImage|TransformParameterError
108     */
109    public function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
110        if ( !$this->normaliseParams( $image, $params ) ) {
111            return new TransformParameterError( $params );
112        }
113
114        // Create a parameter array to pass to the scaler
115        $scalerParams = [
116            // The size to which the image will be resized
117            'physicalWidth' => $params['physicalWidth'],
118            'physicalHeight' => $params['physicalHeight'],
119            'physicalDimensions' => "{$params['physicalWidth']}x{$params['physicalHeight']}",
120            // The size of the image on the page
121            'clientWidth' => $params['width'],
122            'clientHeight' => $params['height'],
123            // Comment as will be added to the Exif of the thumbnail
124            'comment' => isset( $params['descriptionUrl'] )
125                ? "File source: {$params['descriptionUrl']}"
126                : '',
127            // Properties of the original image
128            'srcWidth' => $image->getWidth(),
129            'srcHeight' => $image->getHeight(),
130            'mimeType' => $image->getMimeType(),
131            'dstPath' => $dstPath,
132            'dstUrl' => $dstUrl,
133            'interlace' => $params['interlace'] ?? false,
134        ];
135
136        if ( isset( $params['quality'] ) && $params['quality'] === 'low' ) {
137            $scalerParams['quality'] = 30;
138        }
139
140        // For subclasses that might be paged.
141        if ( $image->isMultipage() && isset( $params['page'] ) ) {
142            $scalerParams['page'] = (int)$params['page'];
143        }
144
145        # Determine scaler type
146        $scaler = $this->getScalerType( $dstPath );
147
148        if ( is_array( $scaler ) ) {
149            $scalerName = get_class( $scaler[0] );
150        } else {
151            $scalerName = $scaler;
152        }
153
154        wfDebug( __METHOD__ . ": creating {$scalerParams['physicalDimensions']} " .
155            "thumbnail of {$image->getPath()} at $dstPath using scaler $scalerName" );
156
157        if ( !$image->mustRender() &&
158            $scalerParams['physicalWidth'] == $scalerParams['srcWidth']
159            && $scalerParams['physicalHeight'] == $scalerParams['srcHeight']
160            && !isset( $scalerParams['quality'] )
161        ) {
162            # normaliseParams (or the user) wants us to return the unscaled image
163            wfDebug( __METHOD__ . ": returning unscaled image" );
164
165            return $this->getClientScalingThumbnailImage( $image, $params );
166        }
167
168        if ( $scaler === 'client' ) {
169            # Client-side image scaling, use the source URL
170            # Using the destination URL in a TRANSFORM_LATER request would be incorrect
171            return $this->getClientScalingThumbnailImage( $image, $params );
172        }
173
174        if ( $image->isTransformedLocally() && !$this->isImageAreaOkForThumbnaling( $image, $params ) ) {
175            $maxImageArea = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::MaxImageArea );
176            return new TransformTooBigImageAreaError( $params, $maxImageArea );
177        }
178
179        if ( $flags & self::TRANSFORM_LATER ) {
180            wfDebug( __METHOD__ . ": Transforming later per flags." );
181            $newParams = [
182                'width' => $scalerParams['clientWidth'],
183                'height' => $scalerParams['clientHeight']
184            ];
185            if ( isset( $params['quality'] ) ) {
186                $newParams['quality'] = $params['quality'];
187            }
188            if ( isset( $params['page'] ) && $params['page'] ) {
189                $newParams['page'] = $params['page'];
190            }
191            return new ThumbnailImage( $image, $dstUrl, false, $newParams );
192        }
193
194        # Try to make a target path for the thumbnail
195        if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) {
196            wfDebug( __METHOD__ . ": Unable to create thumbnail destination " .
197                "directory, falling back to client scaling" );
198
199            return $this->getClientScalingThumbnailImage( $image, $params );
200        }
201
202        # Transform functions and binaries need a FS source file
203        $thumbnailSource = $this->getThumbnailSource( $image, $params );
204
205        // If the source isn't the original, disable EXIF rotation because it's already been applied
206        if ( $scalerParams['srcWidth'] != $thumbnailSource['width']
207            || $scalerParams['srcHeight'] != $thumbnailSource['height'] ) {
208            $scalerParams['disableRotation'] = true;
209        }
210
211        $scalerParams['srcPath'] = $thumbnailSource['path'];
212        $scalerParams['srcWidth'] = $thumbnailSource['width'];
213        $scalerParams['srcHeight'] = $thumbnailSource['height'];
214
215        if ( $scalerParams['srcPath'] === false ) { // Failed to get local copy
216            wfDebugLog( 'thumbnail',
217                sprintf( 'Thumbnail failed on %s: could not get local copy of "%s"',
218                    wfHostname(), $image->getName() ) );
219
220            return new MediaTransformError( 'thumbnail_error',
221                $scalerParams['clientWidth'], $scalerParams['clientHeight'],
222                wfMessage( 'filemissing' )
223            );
224        }
225
226        // Try a hook. Called "Bitmap" for historical reasons.
227        /** @var MediaTransformOutput $mto */
228        $mto = null;
229        ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )
230            ->onBitmapHandlerTransform( $this, $image, $scalerParams, $mto );
231        if ( $mto !== null ) {
232            wfDebug( __METHOD__ . ": Hook to BitmapHandlerTransform created an mto" );
233            $scaler = 'hookaborted';
234        }
235
236        // $scaler will return a MediaTransformError on failure, or false on success.
237        // If the scaler is successful, it will have created a thumbnail at the destination
238        // path.
239        if ( is_array( $scaler ) && is_callable( $scaler ) ) {
240            // Allow subclasses to specify their own rendering methods.
241            $err = $scaler( $image, $scalerParams );
242        } else {
243            switch ( $scaler ) {
244                case 'hookaborted':
245                    # Handled by the hook above
246                    $err = $mto->isError() ? $mto : false;
247                    break;
248                case 'im':
249                    $err = $this->transformImageMagick( $image, $scalerParams );
250                    break;
251                case 'custom':
252                    $err = $this->transformCustom( $image, $scalerParams );
253                    break;
254                case 'imext':
255                    $err = $this->transformImageMagickExt( $image, $scalerParams );
256                    break;
257                case 'gd':
258                default:
259                    $err = $this->transformGd( $image, $scalerParams );
260                    break;
261            }
262        }
263
264        // Remove the file if a zero-byte thumbnail was created, or if there was an error
265        // @phan-suppress-next-line PhanTypeMismatchArgument Relaying on bool/int conversion to cast objects correct
266        $removed = $this->removeBadFile( $dstPath, (bool)$err );
267        if ( $err ) {
268            // transform returned MediaTransformError
269            return $err;
270        }
271
272        if ( $removed ) {
273            // Thumbnail was zero-byte and had to be removed
274            return new MediaTransformError( 'thumbnail_error',
275                $scalerParams['clientWidth'], $scalerParams['clientHeight'],
276                wfMessage( 'unknown-error' )
277            );
278        }
279
280        if ( $mto ) {
281            // @phan-suppress-next-line PhanTypeMismatchReturnSuperType
282            return $mto;
283        }
284
285        $newParams = [
286            'width' => $scalerParams['clientWidth'],
287            'height' => $scalerParams['clientHeight']
288        ];
289        if ( isset( $params['quality'] ) ) {
290            $newParams['quality'] = $params['quality'];
291        }
292        if ( isset( $params['page'] ) && $params['page'] ) {
293            $newParams['page'] = $params['page'];
294        }
295        return new ThumbnailImage( $image, $dstUrl, $dstPath, $newParams );
296    }
297
298    /**
299     * Get the source file for the transform
300     *
301     * @param File $file
302     * @param array $params
303     * @return array Array with keys  width, height and path.
304     */
305    protected function getThumbnailSource( $file, $params ) {
306        return $file->getThumbnailSource( $params );
307    }
308
309    /**
310     * Returns what sort of scaler type should be used.
311     *
312     * Values can be one of client, im, custom, gd, imext, or an array
313     * of object, method-name to call that specific method.
314     *
315     * If specifying a custom scaler command with [ Obj, method ],
316     * the method in question should take 2 parameters, a File object,
317     * and a $scalerParams array with various options (See doTransform
318     * for what is in $scalerParams). On error it should return a
319     * MediaTransformError object. On success it should return false,
320     * and simply make sure the thumbnail file is located at
321     * $scalerParams['dstPath'].
322     *
323     * If there is a problem with the output path, it returns "client"
324     * to do client side scaling.
325     *
326     * @param string|null $dstPath
327     * @param bool $checkDstPath Check that $dstPath is valid
328     * @return string|callable One of client, im, custom, gd, imext, or a callable
329     */
330    abstract protected function getScalerType( $dstPath, $checkDstPath = true );
331
332    /**
333     * Get a ThumbnailImage that represents an image that will be scaled
334     * client side
335     *
336     * @stable to override
337     * @param File $image File associated with this thumbnail
338     * @param array $params Media handler parameters
339     * @return ThumbnailImage
340     *
341     * @todo FIXME: No rotation support
342     */
343    protected function getClientScalingThumbnailImage( $image, $params ) {
344        $url = $image->modifyClientThumbUrl( $image->getUrl(), $params );
345        return new ThumbnailImage( $image, $url, null, $params );
346    }
347
348    /**
349     * Transform an image using ImageMagick
350     *
351     * This is a stub method. The real method is in BitmapHandler.
352     *
353     * @stable to override
354     * @param File $image File associated with this thumbnail
355     * @param array $params Array with scaler params
356     *
357     * @return MediaTransformError|false Error object if error occurred, false (=no error) otherwise
358     */
359    protected function transformImageMagick( $image, $params ) {
360        return $this->getMediaTransformError( $params, "Unimplemented" );
361    }
362
363    /**
364     * Transform an image using the Imagick PHP extension
365     *
366     * This is a stub method. The real method is in BitmapHandler.
367     *
368     * @stable to override
369     * @param File $image File associated with this thumbnail
370     * @param array $params Array with scaler params
371     *
372     * @return MediaTransformError Error object if error occurred, false (=no error) otherwise
373     */
374    protected function transformImageMagickExt( $image, $params ) {
375        return $this->getMediaTransformError( $params, "Unimplemented" );
376    }
377
378    /**
379     * Transform an image using a custom command
380     *
381     * This is a stub method. The real method is in BitmapHandler.
382     *
383     * @stable to override
384     * @param File $image File associated with this thumbnail
385     * @param array $params Array with scaler params
386     *
387     * @return MediaTransformError Error object if error occurred, false (=no error) otherwise
388     */
389    protected function transformCustom( $image, $params ) {
390        return $this->getMediaTransformError( $params, "Unimplemented" );
391    }
392
393    /**
394     * Get a MediaTransformError with error 'thumbnail_error'
395     *
396     * @param array $params Parameter array as passed to the transform* functions
397     * @param string $errMsg Error message
398     * @return MediaTransformError
399     */
400    public function getMediaTransformError( $params, $errMsg ) {
401        return new MediaTransformError( 'thumbnail_error', $params['clientWidth'],
402            $params['clientHeight'], $errMsg );
403    }
404
405    /**
406     * Transform an image using the built in GD library
407     *
408     * This is a stub method. The real method is in BitmapHandler.
409     *
410     * @param File $image File associated with this thumbnail
411     * @param array $params Array with scaler params
412     *
413     * @return MediaTransformError Error object if error occurred, false (=no error) otherwise
414     */
415    protected function transformGd( $image, $params ) {
416        return $this->getMediaTransformError( $params, "Unimplemented" );
417    }
418
419    /**
420     * Escape a string for ImageMagick's property input (e.g. -set -comment)
421     * See InterpretImageProperties() in magick/property.c
422     * @param string $s
423     * @return string
424     */
425    protected function escapeMagickProperty( $s ) {
426        // Double the backslashes
427        $s = str_replace( '\\', '\\\\', $s );
428        // Double the percents
429        $s = str_replace( '%', '%%', $s );
430        // Escape initial - or @
431        if ( strlen( $s ) > 0 && ( $s[0] === '-' || $s[0] === '@' ) ) {
432            $s = '\\' . $s;
433        }
434
435        return $s;
436    }
437
438    /**
439     * Escape a string for ImageMagick's input filenames. See ExpandFilenames()
440     * and GetPathComponent() in magick/utility.c.
441     *
442     * This won't work with an initial ~ or @, so input files should be prefixed
443     * with the directory name.
444     *
445     * Glob character unescaping is broken in ImageMagick before 6.6.1-5, but
446     * it's broken in a way that doesn't involve trying to convert every file
447     * in a directory, so we're better off escaping and waiting for the bugfix
448     * to filter down to users.
449     *
450     * @param string $path The file path
451     * @param string|false $scene The scene specification, or false if there is none
452     * @return string
453     */
454    protected function escapeMagickInput( $path, $scene = false ) {
455        # Die on initial metacharacters (caller should prepend path)
456        $firstChar = substr( $path, 0, 1 );
457        if ( $firstChar === '~' || $firstChar === '@' ) {
458            throw new InvalidArgumentException( __METHOD__ . ': cannot escape this path name' );
459        }
460
461        # Escape glob chars
462        $path = preg_replace( '/[*?\[\]{}]/', '\\\\\0', $path );
463
464        return $this->escapeMagickPath( $path, $scene );
465    }
466
467    /**
468     * Escape a string for ImageMagick's output filename. See
469     * InterpretImageFilename() in magick/image.c.
470     * @param string $path The file path
471     * @param string|false $scene The scene specification, or false if there is none
472     * @return string
473     */
474    protected function escapeMagickOutput( $path, $scene = false ) {
475        $path = str_replace( '%', '%%', $path );
476
477        return $this->escapeMagickPath( $path, $scene );
478    }
479
480    /**
481     * Armour a string against ImageMagick's GetPathComponent(). This is a
482     * helper function for escapeMagickInput() and escapeMagickOutput().
483     *
484     * @param string $path The file path
485     * @param string|false $scene The scene specification, or false if there is none
486     * @return string
487     */
488    protected function escapeMagickPath( $path, $scene = false ) {
489        # Die on format specifiers (other than drive letters). The regex is
490        # meant to match all the formats you get from "convert -list format"
491        if ( preg_match( '/^([a-zA-Z0-9-]+):/', $path, $m ) ) {
492            if ( wfIsWindows() && is_dir( $m[0] ) ) {
493                // OK, it's a drive letter
494                // ImageMagick has a similar exception, see IsMagickConflict()
495            } else {
496                throw new InvalidArgumentException( __METHOD__ . ': unexpected colon character in path name' );
497            }
498        }
499
500        # If there are square brackets, add a do-nothing scene specification
501        # to force a literal interpretation
502        if ( $scene === false ) {
503            if ( str_contains( $path, '[' ) ) {
504                $path .= '[0--1]';
505            }
506        } else {
507            $path .= "[$scene]";
508        }
509
510        return $path;
511    }
512
513    /**
514     * Retrieve the version of the installed ImageMagick
515     * You can use PHPs version_compare() to use this value
516     * Value is cached for one hour.
517     * @return string|false Representing the IM version; false on error
518     */
519    protected function getMagickVersion() {
520        $cache = MediaWikiServices::getInstance()->getLocalServerObjectCache();
521        $method = __METHOD__;
522        return $cache->getWithSetCallback(
523            $cache->makeGlobalKey( 'imagemagick-version' ),
524            $cache::TTL_HOUR,
525            static function () use ( $method ) {
526                $imageMagickConvertCommand = MediaWikiServices::getInstance()
527                    ->getMainConfig()->get( MainConfigNames::ImageMagickConvertCommand );
528
529                $cmd = Shell::escape( $imageMagickConvertCommand ) . ' -version';
530                wfDebug( $method . ": Running convert -version" );
531                $retval = '';
532                $return = wfShellExecWithStderr( $cmd, $retval );
533                $x = preg_match(
534                    '/Version: ImageMagick ([0-9]*\.[0-9]*\.[0-9]*)/', $return, $matches
535                );
536                if ( $x != 1 ) {
537                    wfDebug( $method . ": ImageMagick version check failed" );
538                    return false;
539                }
540
541                return $matches[1];
542            }
543        );
544    }
545
546    /**
547     * Returns whether the current scaler supports rotation.
548     *
549     * @since 1.24 No longer static
550     * @stable to override
551     * @return bool
552     */
553    public function canRotate() {
554        return false;
555    }
556
557    /**
558     * Should we automatically rotate an image based on exif
559     *
560     * @since 1.24 No longer static
561     * @stable to override
562     * @see $wgEnableAutoRotation
563     * @return bool Whether auto rotation is enabled
564     */
565    public function autoRotateEnabled() {
566        return false;
567    }
568
569    /**
570     * Rotate a thumbnail.
571     *
572     * This is a stub. See BitmapHandler::rotate.
573     *
574     * @stable to override
575     * @param File $file
576     * @param array{rotation:int,srcPath:string,dstPath:string} $params Rotate parameters.
577     *   'rotation' clockwise rotation in degrees, allowed are multiples of 90
578     * @since 1.24 Is non-static. From 1.21 it was static
579     * @return MediaTransformError|false
580     */
581    public function rotate( $file, $params ) {
582        return new MediaTransformError( 'thumbnail_error', 0, 0,
583            static::class . ' rotation not implemented' );
584    }
585
586    /**
587     * Returns whether the file needs to be rendered. Returns true if the
588     * file requires rotation and we are able to rotate it.
589     *
590     * @stable to override
591     * @param File $file
592     * @return bool
593     */
594    public function mustRender( $file ) {
595        return $this->canRotate() && $this->getRotation( $file ) != 0;
596    }
597
598    /**
599     * Check if the file is smaller than the maximum image area for thumbnailing.
600     *
601     * Runs the 'BitmapHandlerCheckImageArea' hook.
602     *
603     * @stable to override
604     * @param File $file
605     * @param array &$params
606     * @return bool
607     * @since 1.25
608     */
609    public function isImageAreaOkForThumbnaling( $file, &$params ) {
610        $maxImageArea = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::MaxImageArea );
611
612        # For historical reasons, hook starts with BitmapHandler
613        $checkImageAreaHookResult = null;
614        ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )->onBitmapHandlerCheckImageArea(
615            $file, $params, $checkImageAreaHookResult );
616
617        if ( $checkImageAreaHookResult !== null ) {
618            // was set by hook, so return that value
619            return (bool)$checkImageAreaHookResult;
620        }
621
622        if ( $maxImageArea === false ) {
623            // Checking is disabled, fine to thumbnail
624            return true;
625        }
626
627        $srcWidth = $file->getWidth( $params['page'] );
628        $srcHeight = $file->getHeight( $params['page'] );
629
630        if ( $srcWidth * $srcHeight > $maxImageArea
631            && !( $file->getMimeType() === 'image/jpeg'
632                && $this->getScalerType( null, false ) === 'im' )
633        ) {
634            # Only ImageMagick can efficiently downsize jpg images without loading
635            # the entire file in memory
636            return false;
637        }
638        return true;
639    }
640}
641
642/** @deprecated class alias since 1.46 */
643class_alias( TransformationalImageHandler::class, 'TransformationalImageHandler' );