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