Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
56.72% covered (warning)
56.72%
38 / 67
20.00% covered (danger)
20.00%
2 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
GIFHandler
57.58% covered (warning)
57.58%
38 / 66
20.00% covered (danger)
20.00%
2 / 10
116.15
0.00% covered (danger)
0.00%
0 / 1
 getSizeAndMetadata
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
3.00
 formatMetadata
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getCommonMetaArray
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 getImageArea
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 isAnimatedImage
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 canAnimateThumbnail
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getMetadataType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isFileMetadataValid
81.82% covered (warning)
81.82%
9 / 11
0.00% covered (danger)
0.00%
0 / 1
6.22
 getLongDesc
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
56
 getLength
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2/**
3 * Handler for GIF images.
4 *
5 * @license GPL-2.0-or-later
6 * @file
7 * @ingroup Media
8 */
9
10namespace MediaWiki\Media;
11
12use Exception;
13use MediaWiki\Context\IContextSource;
14use MediaWiki\FileRepo\File\File;
15use MediaWiki\MainConfigNames;
16use MediaWiki\MediaWikiServices;
17use Wikimedia\RequestTimeout\TimeoutException;
18
19/**
20 * Handler for GIF images.
21 *
22 * @ingroup Media
23 */
24class GIFHandler extends BitmapHandler {
25    /**
26     * Value to store in img_metadata if there was error extracting metadata
27     */
28    private const BROKEN_FILE = '0';
29
30    /** @inheritDoc */
31    public function getSizeAndMetadata( $state, $filename ) {
32        try {
33            $parsedGIFMetadata = BitmapMetadataHandler::GIF( $filename );
34        } catch ( TimeoutException $e ) {
35            throw $e;
36        } catch ( Exception $e ) {
37            // Broken file?
38            wfDebug( __METHOD__ . ': ' . $e->getMessage() );
39
40            return [ 'metadata' => [ '_error' => self::BROKEN_FILE ] ];
41        }
42
43        return [
44            'width' => $parsedGIFMetadata['width'],
45            'height' => $parsedGIFMetadata['height'],
46            'bits' => $parsedGIFMetadata['bits'],
47            'metadata' => array_diff_key(
48                $parsedGIFMetadata,
49                [ 'width' => true, 'height' => true, 'bits' => true ]
50            )
51        ];
52    }
53
54    /**
55     * @param File $image
56     * @param IContextSource|false $context
57     * @return array<string,array[]>|false
58     */
59    public function formatMetadata( $image, $context = false ) {
60        $meta = $this->getCommonMetaArray( $image );
61        if ( !$meta ) {
62            return false;
63        }
64
65        return $this->formatMetadataHelper( $meta, $context );
66    }
67
68    /**
69     * Return the standard metadata elements for #filemetadata parser func.
70     * @param File $image
71     * @return array
72     */
73    public function getCommonMetaArray( File $image ) {
74        $meta = $image->getMetadataArray();
75        if ( !isset( $meta['metadata'] ) || !is_array( $meta['metadata'] ) ) {
76            return [];
77        }
78        unset( $meta['metadata']['_MW_GIF_VERSION'] );
79
80        return $meta['metadata'];
81    }
82
83    /**
84     * @todo Add unit tests
85     *
86     * @param File $image
87     * @return int
88     */
89    public function getImageArea( $image ) {
90        $metadata = $image->getMetadataArray();
91        if ( isset( $metadata['frameCount'] ) && $metadata['frameCount'] > 0 ) {
92            return $image->getWidth() * $image->getHeight() * $metadata['frameCount'];
93        }
94        return $image->getWidth() * $image->getHeight();
95    }
96
97    /**
98     * @param File $image
99     * @return bool
100     */
101    public function isAnimatedImage( $image ) {
102        $metadata = $image->getMetadataArray();
103        if ( isset( $metadata['frameCount'] ) && $metadata['frameCount'] > 1 ) {
104            return true;
105        }
106
107        return false;
108    }
109
110    /**
111     * We cannot animate thumbnails that are bigger than a particular size
112     * @param File $file
113     * @return bool
114     */
115    public function canAnimateThumbnail( $file ) {
116        $maxAnimatedGifArea = MediaWikiServices::getInstance()->getMainConfig()
117            ->get( MainConfigNames::MaxAnimatedGifArea );
118
119        return $this->getImageArea( $file ) <= $maxAnimatedGifArea;
120    }
121
122    /** @inheritDoc */
123    public function getMetadataType( $image ) {
124        return 'parsed-gif';
125    }
126
127    /** @inheritDoc */
128    public function isFileMetadataValid( $image ) {
129        $data = $image->getMetadataArray();
130        if ( $data === [ '_error' => self::BROKEN_FILE ] ) {
131            // Do not repetitively regenerate metadata on broken file.
132            return self::METADATA_GOOD;
133        }
134
135        if ( !$data || isset( $data['_error'] ) ) {
136            wfDebug( __METHOD__ . " invalid GIF metadata" );
137
138            return self::METADATA_BAD;
139        }
140
141        if ( !isset( $data['metadata']['_MW_GIF_VERSION'] )
142            || $data['metadata']['_MW_GIF_VERSION'] !== GIFMetadataExtractor::VERSION
143        ) {
144            wfDebug( __METHOD__ . " old but compatible GIF metadata" );
145
146            return self::METADATA_COMPATIBLE;
147        }
148
149        return self::METADATA_GOOD;
150    }
151
152    /**
153     * @param File $image
154     * @return string
155     */
156    public function getLongDesc( $image ) {
157        $lang = $this->getLanguage();
158        $original = parent::getLongDesc( $image );
159
160        $metadata = $image->getMetadataArray();
161
162        if ( !$metadata || isset( $metadata['_error'] ) || $metadata['frameCount'] <= 0 ) {
163            return $original;
164        }
165
166        /* Preserve original image info string, but strip the last char ')' so we can add even more */
167        $info = [];
168        $info[] = $original;
169
170        if ( $metadata['looped'] ) {
171            $info[] = wfMessage( 'file-info-gif-looped' )->inLanguage( $lang )->parse();
172        }
173
174        if ( $metadata['frameCount'] > 1 ) {
175            $info[] = wfMessage( 'file-info-gif-frames' )->numParams( $metadata['frameCount'] )
176                ->inLanguage( $lang )->parse();
177        }
178
179        if ( $metadata['duration'] ) {
180            $info[] = htmlspecialchars( $lang->formatTimePeriod( $metadata['duration'] ), ENT_QUOTES );
181        }
182
183        return $lang->commaList( $info );
184    }
185
186    /**
187     * Return the duration of the GIF file.
188     *
189     * Shown in the &query=imageinfo&iiprop=size api query.
190     *
191     * @param File $file
192     * @return float The duration of the file.
193     */
194    public function getLength( $file ) {
195        $metadata = $file->getMetadataArray();
196
197        if ( !$metadata || !isset( $metadata['duration'] ) || !$metadata['duration'] ) {
198            return 0.0;
199        }
200        return (float)$metadata['duration'];
201    }
202}
203
204/** @deprecated class alias since 1.46 */
205class_alias( GIFHandler::class, 'GIFHandler' );