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