Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
54.55% |
42 / 77 |
|
0.00% |
0 / 8 |
CRAP | |
0.00% |
0 / 1 |
ExifBitmapHandler | |
54.55% |
42 / 77 |
|
0.00% |
0 / 8 |
216.65 | |
0.00% |
0 / 1 |
convertMetadataVersion | |
80.00% |
20 / 25 |
|
0.00% |
0 / 1 |
19.31 | |||
isFileMetadataValid | |
85.71% |
18 / 21 |
|
0.00% |
0 / 1 |
9.24 | |||
formatMetadata | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getCommonMetaArray | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
2.03 | |||
getMetadataType | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
applyExifRotation | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
getRotation | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getRotationForExifFromOrientation | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
42 |
1 | <?php |
2 | |
3 | /** |
4 | * Handler for bitmap images with exif metadata. |
5 | * |
6 | * This program is free software; you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License as published by |
8 | * the Free Software Foundation; either version 2 of the License, or |
9 | * (at your option) any later version. |
10 | * |
11 | * This program is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | * GNU General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU General Public License along |
17 | * with this program; if not, write to the Free Software Foundation, Inc., |
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
19 | * http://www.gnu.org/copyleft/gpl.html |
20 | * |
21 | * @file |
22 | * @ingroup Media |
23 | */ |
24 | |
25 | use MediaWiki\Context\IContextSource; |
26 | use MediaWiki\FileRepo\File\File; |
27 | use MediaWiki\MainConfigNames; |
28 | use MediaWiki\MediaWikiServices; |
29 | |
30 | /** |
31 | * Stuff specific to JPEG and (built-in) TIFF handler. |
32 | * All metadata related, since both JPEG and TIFF support Exif. |
33 | * |
34 | * @stable to extend |
35 | * @ingroup Media |
36 | */ |
37 | class ExifBitmapHandler extends BitmapHandler { |
38 | /** Error extracting metadata */ |
39 | public const BROKEN_FILE = '-1'; |
40 | |
41 | /** Outdated error extracting metadata */ |
42 | public const OLD_BROKEN_FILE = '0'; |
43 | |
44 | public function convertMetadataVersion( $metadata, $version = 1 ) { |
45 | // basically flattens arrays. |
46 | $version = is_int( $version ) ? $version : (int)explode( ';', $version, 2 )[0]; |
47 | if ( $version < 1 || $version >= 2 ) { |
48 | return $metadata; |
49 | } |
50 | |
51 | if ( !isset( $metadata['MEDIAWIKI_EXIF_VERSION'] ) || $metadata['MEDIAWIKI_EXIF_VERSION'] !== 2 ) { |
52 | return $metadata; |
53 | } |
54 | |
55 | // Treat Software as a special case because in can contain |
56 | // an array of (SoftwareName, Version). |
57 | if ( isset( $metadata['Software'] ) |
58 | && is_array( $metadata['Software'] ) |
59 | && is_array( $metadata['Software'][0] ) |
60 | && isset( $metadata['Software'][0][0] ) |
61 | && isset( $metadata['Software'][0][1] ) |
62 | ) { |
63 | $metadata['Software'] = $metadata['Software'][0][0] . ' (Version ' |
64 | . $metadata['Software'][0][1] . ')'; |
65 | } |
66 | |
67 | $formatter = new FormatMetadata; |
68 | |
69 | // ContactInfo also has to be dealt with specially |
70 | if ( isset( $metadata['Contact'] ) ) { |
71 | $metadata['Contact'] = $formatter->collapseContactInfo( |
72 | is_array( $metadata['Contact'] ) ? $metadata['Contact'] : [ $metadata['Contact'] ] |
73 | ); |
74 | } |
75 | |
76 | // Ignore Location shown if it is not a simple string |
77 | if ( isset( $metadata['LocationShown'] ) && !is_string( $metadata['LocationShown'] ) ) { |
78 | unset( $metadata['LocationShown'] ); |
79 | } |
80 | |
81 | foreach ( $metadata as &$val ) { |
82 | if ( is_array( $val ) ) { |
83 | // @phan-suppress-next-line SecurityCheck-DoubleEscaped Ambiguous with the true for nohtml |
84 | $val = $formatter->flattenArrayReal( $val, 'ul', true ); |
85 | } |
86 | } |
87 | unset( $val ); |
88 | $metadata['MEDIAWIKI_EXIF_VERSION'] = 1; |
89 | |
90 | return $metadata; |
91 | } |
92 | |
93 | /** |
94 | * @param File $image |
95 | * @return bool|int |
96 | */ |
97 | public function isFileMetadataValid( $image ) { |
98 | $showEXIF = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::ShowEXIF ); |
99 | if ( !$showEXIF ) { |
100 | # Metadata disabled and so an empty field is expected |
101 | return self::METADATA_GOOD; |
102 | } |
103 | $exif = $image->getMetadataArray(); |
104 | if ( !$exif ) { |
105 | wfDebug( __METHOD__ . ': error unserializing?' ); |
106 | return self::METADATA_BAD; |
107 | } |
108 | if ( $exif === [ '_error' => self::OLD_BROKEN_FILE ] ) { |
109 | # Old special value indicating that there is no Exif data in the file. |
110 | # or that there was an error well extracting the metadata. |
111 | wfDebug( __METHOD__ . ": back-compat version" ); |
112 | return self::METADATA_COMPATIBLE; |
113 | } |
114 | |
115 | if ( $exif === [ '_error' => self::BROKEN_FILE ] ) { |
116 | return self::METADATA_GOOD; |
117 | } |
118 | |
119 | if ( !isset( $exif['MEDIAWIKI_EXIF_VERSION'] ) |
120 | || $exif['MEDIAWIKI_EXIF_VERSION'] !== Exif::version() |
121 | ) { |
122 | if ( isset( $exif['MEDIAWIKI_EXIF_VERSION'] ) |
123 | && $exif['MEDIAWIKI_EXIF_VERSION'] === 1 |
124 | ) { |
125 | // back-compatible but old |
126 | wfDebug( __METHOD__ . ": back-compat version" ); |
127 | |
128 | return self::METADATA_COMPATIBLE; |
129 | } |
130 | # Wrong (non-compatible) version |
131 | wfDebug( __METHOD__ . ": wrong version" ); |
132 | |
133 | return self::METADATA_BAD; |
134 | } |
135 | |
136 | return self::METADATA_GOOD; |
137 | } |
138 | |
139 | /** |
140 | * @param File $image |
141 | * @param IContextSource|false $context |
142 | * @return array[]|false |
143 | */ |
144 | public function formatMetadata( $image, $context = false ) { |
145 | $meta = $this->getCommonMetaArray( $image ); |
146 | if ( !$meta ) { |
147 | return false; |
148 | } |
149 | |
150 | return $this->formatMetadataHelper( $meta, $context ); |
151 | } |
152 | |
153 | public function getCommonMetaArray( File $file ) { |
154 | $exif = $file->getMetadataArray(); |
155 | if ( !$exif ) { |
156 | return []; |
157 | } |
158 | unset( $exif['MEDIAWIKI_EXIF_VERSION'] ); |
159 | |
160 | return $exif; |
161 | } |
162 | |
163 | public function getMetadataType( $image ) { |
164 | return 'exif'; |
165 | } |
166 | |
167 | protected function applyExifRotation( $info, $metadata ) { |
168 | if ( $this->autoRotateEnabled() ) { |
169 | $rotation = $this->getRotationForExifFromOrientation( $metadata['Orientation'] ?? null ); |
170 | } else { |
171 | $rotation = 0; |
172 | } |
173 | |
174 | if ( $rotation === 90 || $rotation === 270 ) { |
175 | $width = $info['width']; |
176 | $info['width'] = $info['height']; |
177 | $info['height'] = $width; |
178 | } |
179 | return $info; |
180 | } |
181 | |
182 | /** |
183 | * On supporting image formats, try to read out the low-level orientation |
184 | * of the file and return the angle that the file needs to be rotated to |
185 | * be viewed. |
186 | * |
187 | * This information is only useful when manipulating the original file; |
188 | * the width and height we normally work with is logical, and will match |
189 | * any produced output views. |
190 | * |
191 | * @param File $file |
192 | * @return int 0, 90, 180 or 270 |
193 | */ |
194 | public function getRotation( $file ) { |
195 | if ( !$this->autoRotateEnabled() ) { |
196 | return 0; |
197 | } |
198 | |
199 | $orientation = $file->getMetadataItem( 'Orientation' ); |
200 | return $this->getRotationForExifFromOrientation( $orientation ); |
201 | } |
202 | |
203 | /** |
204 | * Given a chunk of serialized Exif metadata, return the orientation as |
205 | * degrees of rotation. |
206 | * |
207 | * @param int|null $orientation |
208 | * @return int 0, 90, 180 or 270 |
209 | * @todo FIXME: Orientation can include flipping as well; see if this is an issue! |
210 | */ |
211 | protected function getRotationForExifFromOrientation( $orientation ) { |
212 | if ( $orientation === null ) { |
213 | return 0; |
214 | } |
215 | # See http://sylvana.net/jpegcrop/exif_orientation.html |
216 | switch ( $orientation ) { |
217 | case 8: |
218 | return 90; |
219 | case 3: |
220 | return 180; |
221 | case 6: |
222 | return 270; |
223 | default: |
224 | return 0; |
225 | } |
226 | } |
227 | } |