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