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 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 * @ingroup Media
22 */
23
24use MediaWiki\MainConfigNames;
25use MediaWiki\MediaWikiServices;
26use Wikimedia\RequestTimeout\TimeoutException;
27
28/**
29 * Handler for GIF images.
30 *
31 * @ingroup Media
32 */
33class GIFHandler extends BitmapHandler {
34    /**
35     * Value to store in img_metadata if there was error extracting metadata
36     */
37    private const BROKEN_FILE = '0';
38
39    public function getSizeAndMetadata( $state, $filename ) {
40        try {
41            $parsedGIFMetadata = BitmapMetadataHandler::GIF( $filename );
42        } catch ( TimeoutException $e ) {
43            throw $e;
44        } catch ( Exception $e ) {
45            // Broken file?
46            wfDebug( __METHOD__ . ': ' . $e->getMessage() );
47
48            return [ 'metadata' => [ '_error' => self::BROKEN_FILE ] ];
49        }
50
51        return [
52            'width' => $parsedGIFMetadata['width'],
53            'height' => $parsedGIFMetadata['height'],
54            'bits' => $parsedGIFMetadata['bits'],
55            'metadata' => array_diff_key(
56                $parsedGIFMetadata,
57                [ 'width' => true, 'height' => true, 'bits' => true ]
58            )
59        ];
60    }
61
62    /**
63     * @param File $image
64     * @param IContextSource|false $context
65     * @return array[]|false
66     */
67    public function formatMetadata( $image, $context = false ) {
68        $meta = $this->getCommonMetaArray( $image );
69        if ( !$meta ) {
70            return false;
71        }
72
73        return $this->formatMetadataHelper( $meta, $context );
74    }
75
76    /**
77     * Return the standard metadata elements for #filemetadata parser func.
78     * @param File $image
79     * @return array
80     */
81    public function getCommonMetaArray( File $image ) {
82        $meta = $image->getMetadataArray();
83        if ( !isset( $meta['metadata'] ) ) {
84            return [];
85        }
86        unset( $meta['metadata']['_MW_GIF_VERSION'] );
87
88        return $meta['metadata'];
89    }
90
91    /**
92     * @todo Add unit tests
93     *
94     * @param File $image
95     * @return int
96     */
97    public function getImageArea( $image ) {
98        $metadata = $image->getMetadataArray();
99        if ( isset( $metadata['frameCount'] ) && $metadata['frameCount'] > 0 ) {
100            return $image->getWidth() * $image->getHeight() * $metadata['frameCount'];
101        }
102        return $image->getWidth() * $image->getHeight();
103    }
104
105    /**
106     * @param File $image
107     * @return bool
108     */
109    public function isAnimatedImage( $image ) {
110        $metadata = $image->getMetadataArray();
111        if ( isset( $metadata['frameCount'] ) && $metadata['frameCount'] > 1 ) {
112            return true;
113        }
114
115        return false;
116    }
117
118    /**
119     * We cannot animate thumbnails that are bigger than a particular size
120     * @param File $file
121     * @return bool
122     */
123    public function canAnimateThumbnail( $file ) {
124        $maxAnimatedGifArea = MediaWikiServices::getInstance()->getMainConfig()
125            ->get( MainConfigNames::MaxAnimatedGifArea );
126
127        return $this->getImageArea( $file ) <= $maxAnimatedGifArea;
128    }
129
130    public function getMetadataType( $image ) {
131        return 'parsed-gif';
132    }
133
134    public function isFileMetadataValid( $image ) {
135        $data = $image->getMetadataArray();
136        if ( $data === [ '_error' => self::BROKEN_FILE ] ) {
137            // Do not repetitively regenerate metadata on broken file.
138            return self::METADATA_GOOD;
139        }
140
141        if ( !$data || isset( $data['_error'] ) ) {
142            wfDebug( __METHOD__ . " invalid GIF metadata" );
143
144            return self::METADATA_BAD;
145        }
146
147        if ( !isset( $data['metadata']['_MW_GIF_VERSION'] )
148            || $data['metadata']['_MW_GIF_VERSION'] !== GIFMetadataExtractor::VERSION
149        ) {
150            wfDebug( __METHOD__ . " old but compatible GIF metadata" );
151
152            return self::METADATA_COMPATIBLE;
153        }
154
155        return self::METADATA_GOOD;
156    }
157
158    /**
159     * @param File $image
160     * @return string
161     */
162    public function getLongDesc( $image ) {
163        global $wgLang;
164
165        $original = parent::getLongDesc( $image );
166
167        $metadata = $image->getMetadataArray();
168
169        if ( !$metadata || isset( $metadata['_error'] ) || $metadata['frameCount'] <= 0 ) {
170            return $original;
171        }
172
173        /* Preserve original image info string, but strip the last char ')' so we can add even more */
174        $info = [];
175        $info[] = $original;
176
177        if ( $metadata['looped'] ) {
178            $info[] = wfMessage( 'file-info-gif-looped' )->parse();
179        }
180
181        if ( $metadata['frameCount'] > 1 ) {
182            $info[] = wfMessage( 'file-info-gif-frames' )->numParams( $metadata['frameCount'] )->parse();
183        }
184
185        if ( $metadata['duration'] ) {
186            $info[] = $wgLang->formatTimePeriod( $metadata['duration'] );
187        }
188
189        return $wgLang->commaList( $info );
190    }
191
192    /**
193     * Return the duration of the GIF file.
194     *
195     * Shown in the &query=imageinfo&iiprop=size api query.
196     *
197     * @param File $file
198     * @return float The duration of the file.
199     */
200    public function getLength( $file ) {
201        $metadata = $file->getMetadataArray();
202
203        if ( !$metadata || !isset( $metadata['duration'] ) || !$metadata['duration'] ) {
204            return 0.0;
205        }
206        return (float)$metadata['duration'];
207    }
208}