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