]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/Exif.php
MediaWiki 1.15.0
[autoinstallsdev/mediawiki.git] / includes / Exif.php
1 <?php
2 /**
3  * This program is free software; you can redistribute it and/or modify
4  * it under the terms of the GNU General Public License as published by
5  * the Free Software Foundation; either version 2 of the License, or
6  * (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16  * http://www.gnu.org/copyleft/gpl.html
17  *
18  * @ingroup Media
19  * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
20  * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason
21  * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
22  * @see http://exif.org/Exif2-2.PDF The Exif 2.2 specification
23  * @file
24  */
25
26 /**
27  * @todo document (e.g. one-sentence class-overview description)
28  * @ingroup Media
29  */
30 class Exif {
31
32         const BYTE      = 1;    //!< An 8-bit (1-byte) unsigned integer.
33         const ASCII     = 2;    //!< An 8-bit byte containing one 7-bit ASCII code. The final byte is terminated with NULL.
34         const SHORT     = 3;    //!< A 16-bit (2-byte) unsigned integer.
35         const LONG      = 4;    //!< A 32-bit (4-byte) unsigned integer.
36         const RATIONAL  = 5;    //!< Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator
37         const UNDEFINED = 7;    //!< An 8-bit byte that can take any value depending on the field definition
38         const SLONG     = 9;    //!< A 32-bit (4-byte) signed integer (2's complement notation),
39         const SRATIONAL = 10;   //!< Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator.
40
41         //@{
42         /* @var array
43          * @private
44          */
45
46         /**
47          * Exif tags grouped by category, the tagname itself is the key and the type
48          * is the value, in the case of more than one possible value type they are
49          * separated by commas.
50          */
51         var $mExifTags;
52
53         /**
54          * A one dimentional array of all Exif tags
55          */
56         var $mFlatExifTags;
57
58         /**
59          * The raw Exif data returned by exif_read_data()
60          */
61         var $mRawExifData;
62
63         /**
64          * A Filtered version of $mRawExifData that has been pruned of invalid
65          * tags and tags that contain content they shouldn't contain according
66          * to the Exif specification
67          */
68         var $mFilteredExifData;
69
70         /**
71          * Filtered and formatted Exif data, see FormatExif::getFormattedData()
72          */
73         var $mFormattedExifData;
74
75         //@}
76
77         //@{
78         /* @var string
79          * @private
80          */
81
82         /**
83          * The file being processed
84          */
85         var $file;
86
87         /**
88          * The basename of the file being processed
89          */
90         var $basename;
91
92         /**
93          * The private log to log to, e.g. 'exif'
94          */
95         var $log = false;
96
97         //@}
98
99         /**
100          * Constructor
101          *
102          * @param $file String: filename.
103          */
104         function __construct( $file ) {
105                 /**
106                  * Page numbers here refer to pages in the EXIF 2.2 standard
107                  *
108                  * @link http://exif.org/Exif2-2.PDF The Exif 2.2 specification
109                  */
110                 $this->mExifTags = array(
111                         # TIFF Rev. 6.0 Attribute Information (p22)
112                         'tiff' => array(
113                                 # Tags relating to image structure
114                                 'structure' => array(
115                                         'ImageWidth' => Exif::SHORT.','.Exif::LONG,             # Image width
116                                         'ImageLength' => Exif::SHORT.','.Exif::LONG,    # Image height
117                                         'BitsPerSample' => Exif::SHORT,                 # Number of bits per component
118                                         # "When a primary image is JPEG compressed, this designation is not"
119                                         # "necessary and is omitted." (p23)
120                                         'Compression' => Exif::SHORT,                           # Compression scheme #p23
121                                         'PhotometricInterpretation' => Exif::SHORT,             # Pixel composition #p23
122                                         'Orientation' => Exif::SHORT,                           # Orientation of image #p24
123                                         'SamplesPerPixel' => Exif::SHORT,                       # Number of components
124                                         'PlanarConfiguration' => Exif::SHORT,                   # Image data arrangement #p24
125                                         'YCbCrSubSampling' => Exif::SHORT,                      # Subsampling ratio of Y to C #p24
126                                         'YCbCrPositioning' => Exif::SHORT,                      # Y and C positioning #p24-25
127                                         'XResolution' => Exif::RATIONAL,                        # Image resolution in width direction
128                                         'YResolution' => Exif::RATIONAL,                        # Image resolution in height direction
129                                         'ResolutionUnit' => Exif::SHORT,                        # Unit of X and Y resolution #(p26)
130                                 ),
131
132                                 # Tags relating to recording offset
133                                 'offset' => array(
134                                         'StripOffsets' => Exif::SHORT.','.Exif::LONG,                   # Image data location
135                                         'RowsPerStrip' => Exif::SHORT.','.Exif::LONG,                   # Number of rows per strip
136                                         'StripByteCounts' => Exif::SHORT.','.Exif::LONG,                        # Bytes per compressed strip
137                                         'JPEGInterchangeFormat' => Exif::SHORT.','.Exif::LONG,          # Offset to JPEG SOI
138                                         'JPEGInterchangeFormatLength' => Exif::SHORT.','.Exif::LONG,    # Bytes of JPEG data
139                                 ),
140
141                                 # Tags relating to image data characteristics
142                                 'characteristics' => array(
143                                         'TransferFunction' => Exif::SHORT,              # Transfer function
144                                         'WhitePoint' => Exif::RATIONAL,         # White point chromaticity
145                                         'PrimaryChromaticities' => Exif::RATIONAL,      # Chromaticities of primarities
146                                         'YCbCrCoefficients' => Exif::RATIONAL,  # Color space transformation matrix coefficients #p27
147                                         'ReferenceBlackWhite' => Exif::RATIONAL # Pair of black and white reference values
148                                 ),
149
150                                 # Other tags
151                                 'other' => array(
152                                         'DateTime' => Exif::ASCII,            # File change date and time
153                                         'ImageDescription' => Exif::ASCII,    # Image title
154                                         'Make' => Exif::ASCII,                # Image input equipment manufacturer
155                                         'Model' => Exif::ASCII,               # Image input equipment model
156                                         'Software' => Exif::ASCII,            # Software used
157                                         'Artist' => Exif::ASCII,              # Person who created the image
158                                         'Copyright' => Exif::ASCII,           # Copyright holder
159                                 ),
160                         ),
161
162                         # Exif IFD Attribute Information (p30-31)
163                         'exif' => array(
164                                 # Tags relating to version
165                                 'version' => array(
166                                         # TODO: NOTE: Nonexistence of this field is taken to mean nonconformance
167                                         # to the EXIF 2.1 AND 2.2 standards
168                                         'ExifVersion' => Exif::UNDEFINED,       # Exif version
169                                         'FlashpixVersion' => Exif::UNDEFINED, # Supported Flashpix version #p32
170                                 ),
171
172                                 # Tags relating to Image Data Characteristics
173                                 'characteristics' => array(
174                                         'ColorSpace' => Exif::SHORT,            # Color space information #p32
175                                 ),
176
177                                 # Tags relating to image configuration
178                                 'configuration' => array(
179                                         'ComponentsConfiguration' => Exif::UNDEFINED,           # Meaning of each component #p33
180                                         'CompressedBitsPerPixel' => Exif::RATIONAL,             # Image compression mode
181                                         'PixelYDimension' => Exif::SHORT.','.Exif::LONG,        # Valid image width
182                                         'PixelXDimension' => Exif::SHORT.','.Exif::LONG,        # Valind image height
183                                 ),
184
185                                 # Tags relating to related user information
186                                 'user' => array(
187                                         'MakerNote' => Exif::UNDEFINED,                 # Manufacturer notes
188                                         'UserComment' => Exif::UNDEFINED,                       # User comments #p34
189                                 ),
190
191                                 # Tags relating to related file information
192                                 'related' => array(
193                                         'RelatedSoundFile' => Exif::ASCII,                      # Related audio file
194                                 ),
195
196                                 # Tags relating to date and time
197                                 'dateandtime' => array(
198                                         'DateTimeOriginal' => Exif::ASCII,                      # Date and time of original data generation #p36
199                                         'DateTimeDigitized' => Exif::ASCII,                     # Date and time of original data generation
200                                         'SubSecTime' => Exif::ASCII,                            # DateTime subseconds
201                                         'SubSecTimeOriginal' => Exif::ASCII,                    # DateTimeOriginal subseconds
202                                         'SubSecTimeDigitized' => Exif::ASCII,                   # DateTimeDigitized subseconds
203                                 ),
204
205                                 # Tags relating to picture-taking conditions (p31)
206                                 'conditions' => array(
207                                         'ExposureTime' => Exif::RATIONAL,                       # Exposure time
208                                         'FNumber' => Exif::RATIONAL,                            # F Number
209                                         'ExposureProgram' => Exif::SHORT,                       # Exposure Program #p38
210                                         'SpectralSensitivity' => Exif::ASCII,                   # Spectral sensitivity
211                                         'ISOSpeedRatings' => Exif::SHORT,                       # ISO speed rating
212                                         'OECF' => Exif::UNDEFINED,                              # Optoelectronic conversion factor
213                                         'ShutterSpeedValue' => Exif::SRATIONAL,         # Shutter speed
214                                         'ApertureValue' => Exif::RATIONAL,                      # Aperture
215                                         'BrightnessValue' => Exif::SRATIONAL,                   # Brightness
216                                         'ExposureBiasValue' => Exif::SRATIONAL,         # Exposure bias
217                                         'MaxApertureValue' => Exif::RATIONAL,                   # Maximum land aperture
218                                         'SubjectDistance' => Exif::RATIONAL,                    # Subject distance
219                                         'MeteringMode' => Exif::SHORT,                  # Metering mode #p40
220                                         'LightSource' => Exif::SHORT,                           # Light source #p40-41
221                                         'Flash' => Exif::SHORT,                         # Flash #p41-42
222                                         'FocalLength' => Exif::RATIONAL,                        # Lens focal length
223                                         'SubjectArea' => Exif::SHORT,                           # Subject area
224                                         'FlashEnergy' => Exif::RATIONAL,                        # Flash energy
225                                         'SpatialFrequencyResponse' => Exif::UNDEFINED,  # Spatial frequency response
226                                         'FocalPlaneXResolution' => Exif::RATIONAL,              # Focal plane X resolution
227                                         'FocalPlaneYResolution' => Exif::RATIONAL,              # Focal plane Y resolution
228                                         'FocalPlaneResolutionUnit' => Exif::SHORT,              # Focal plane resolution unit #p46
229                                         'SubjectLocation' => Exif::SHORT,                       # Subject location
230                                         'ExposureIndex' => Exif::RATIONAL,                      # Exposure index
231                                         'SensingMethod' => Exif::SHORT,                 # Sensing method #p46
232                                         'FileSource' => Exif::UNDEFINED,                        # File source #p47
233                                         'SceneType' => Exif::UNDEFINED,                 # Scene type #p47
234                                         'CFAPattern' => Exif::UNDEFINED,                        # CFA pattern
235                                         'CustomRendered' => Exif::SHORT,                        # Custom image processing #p48
236                                         'ExposureMode' => Exif::SHORT,                  # Exposure mode #p48
237                                         'WhiteBalance' => Exif::SHORT,                  # White Balance #p49
238                                         'DigitalZoomRatio' => Exif::RATIONAL,                   # Digital zoom ration
239                                         'FocalLengthIn35mmFilm' => Exif::SHORT,         # Focal length in 35 mm film
240                                         'SceneCaptureType' => Exif::SHORT,                      # Scene capture type #p49
241                                         'GainControl' => Exif::RATIONAL,                        # Scene control #p49-50
242                                         'Contrast' => Exif::SHORT,                              # Contrast #p50
243                                         'Saturation' => Exif::SHORT,                            # Saturation #p50
244                                         'Sharpness' => Exif::SHORT,                             # Sharpness #p50
245                                         'DeviceSettingDescription' => Exif::UNDEFINED,  # Desice settings description
246                                         'SubjectDistanceRange' => Exif::SHORT,          # Subject distance range #p51
247                                 ),
248
249                                 'other' => array(
250                                         'ImageUniqueID' => Exif::ASCII, # Unique image ID
251                                 ),
252                         ),
253
254                         # GPS Attribute Information (p52)
255                         'gps' => array(
256                                 'GPSVersionID' => Exif::BYTE,                   # GPS tag version
257                                 'GPSLatitudeRef' => Exif::ASCII,                # North or South Latitude #p52-53
258                                 'GPSLatitude' => Exif::RATIONAL,                # Latitude
259                                 'GPSLongitudeRef' => Exif::ASCII,               # East or West Longitude #p53
260                                 'GPSLongitude' => Exif::RATIONAL,               # Longitude
261                                 'GPSAltitudeRef' => Exif::BYTE,         # Altitude reference
262                                 'GPSAltitude' => Exif::RATIONAL,                # Altitude
263                                 'GPSTimeStamp' => Exif::RATIONAL,               # GPS time (atomic clock)
264                                 'GPSSatellites' => Exif::ASCII,         # Satellites used for measurement
265                                 'GPSStatus' => Exif::ASCII,                     # Receiver status #p54
266                                 'GPSMeasureMode' => Exif::ASCII,                # Measurement mode #p54-55
267                                 'GPSDOP' => Exif::RATIONAL,                     # Measurement precision
268                                 'GPSSpeedRef' => Exif::ASCII,                   # Speed unit #p55
269                                 'GPSSpeed' => Exif::RATIONAL,                   # Speed of GPS receiver
270                                 'GPSTrackRef' => Exif::ASCII,                   # Reference for direction of movement #p55
271                                 'GPSTrack' => Exif::RATIONAL,                   # Direction of movement
272                                 'GPSImgDirectionRef' => Exif::ASCII,            # Reference for direction of image #p56
273                                 'GPSImgDirection' => Exif::RATIONAL,            # Direction of image
274                                 'GPSMapDatum' => Exif::ASCII,                   # Geodetic survey data used
275                                 'GPSDestLatitudeRef' => Exif::ASCII,            # Reference for latitude of destination #p56
276                                 'GPSDestLatitude' => Exif::RATIONAL,            # Latitude destination
277                                 'GPSDestLongitudeRef' => Exif::ASCII,           # Reference for longitude of destination #p57
278                                 'GPSDestLongitude' => Exif::RATIONAL,           # Longitude of destination
279                                 'GPSDestBearingRef' => Exif::ASCII,             # Reference for bearing of destination #p57
280                                 'GPSDestBearing' => Exif::RATIONAL,             # Bearing of destination
281                                 'GPSDestDistanceRef' => Exif::ASCII,            # Reference for distance to destination #p57-58
282                                 'GPSDestDistance' => Exif::RATIONAL,            # Distance to destination
283                                 'GPSProcessingMethod' => Exif::UNDEFINED,       # Name of GPS processing method
284                                 'GPSAreaInformation' => Exif::UNDEFINED,        # Name of GPS area
285                                 'GPSDateStamp' => Exif::ASCII,          # GPS date
286                                 'GPSDifferential' => Exif::SHORT,               # GPS differential correction
287                         ),
288                 );
289
290                 $this->file = $file;
291                 $this->basename = wfBaseName( $this->file );
292
293                 $this->makeFlatExifTags();
294
295                 $this->debugFile( $this->basename, __FUNCTION__, true );
296                 wfSuppressWarnings();
297                 $data = exif_read_data( $this->file );
298                 wfRestoreWarnings();
299                 /**
300                  * exif_read_data() will return false on invalid input, such as
301                  * when somebody uploads a file called something.jpeg
302                  * containing random gibberish.
303                  */
304                 $this->mRawExifData = $data ? $data : array();
305
306                 $this->makeFilteredData();
307                 $this->makeFormattedData();
308
309                 $this->debugFile( __FUNCTION__, false );
310         }
311
312         /**#@+
313          * @private
314          */
315         /**
316          * Generate a flat list of the exif tags
317          */
318         function makeFlatExifTags() {
319                 $this->extractTags( $this->mExifTags );
320         }
321
322         /**
323          * A recursing extractor function used by makeFlatExifTags()
324          *
325          * Note: This used to use an array_walk function, but it made PHP5
326          * segfault, see `cvs diff -u -r 1.4 -r 1.5 Exif.php`
327          */
328         function extractTags( &$tagset ) {
329                 foreach( $tagset as $key => $val ) {
330                         if( is_array( $val ) ) {
331                                 $this->extractTags( $val );
332                         } else {
333                                 $this->mFlatExifTags[$key] = $val;
334                         }
335                 }
336         }
337
338         /**
339          * Make $this->mFilteredExifData
340          */
341         function makeFilteredData() {
342                 $this->mFilteredExifData = $this->mRawExifData;
343
344                 foreach( $this->mFilteredExifData as $k => $v ) {
345                         if ( !in_array( $k, array_keys( $this->mFlatExifTags ) ) ) {
346                                 $this->debug( $v, __FUNCTION__, "'$k' is not a valid Exif tag" );
347                                 unset( $this->mFilteredExifData[$k] );
348                         }
349                 }
350
351                 foreach( $this->mFilteredExifData as $k => $v ) {
352                         if ( !$this->validate($k, $v) ) {
353                                 $this->debug( $v, __FUNCTION__, "'$k' contained invalid data" );
354                                 unset( $this->mFilteredExifData[$k] );
355                         }
356                 }
357         }
358
359         /**
360          * @todo document
361          */
362         function makeFormattedData( ) {
363                 $format = new FormatExif( $this->getFilteredData() );
364                 $this->mFormattedExifData = $format->getFormattedData();
365         }
366         /**#@-*/
367
368         /**#@+
369          * @return array
370          */
371         /**
372          * Get $this->mRawExifData
373          */
374         function getData() {
375                 return $this->mRawExifData;
376         }
377
378         /**
379          * Get $this->mFilteredExifData
380          */
381         function getFilteredData() {
382                 return $this->mFilteredExifData;
383         }
384
385         /**
386          * Get $this->mFormattedExifData
387          */
388         function getFormattedData() {
389                 return $this->mFormattedExifData;
390         }
391         /**#@-*/
392
393         /**
394          * The version of the output format
395          *
396          * Before the actual metadata information is saved in the database we
397          * strip some of it since we don't want to save things like thumbnails
398          * which usually accompany Exif data. This value gets saved in the
399          * database along with the actual Exif data, and if the version in the
400          * database doesn't equal the value returned by this function the Exif
401          * data is regenerated.
402          *
403          * @return int
404          */
405         public static function version() {
406                 return 1; // We don't need no bloddy constants!
407         }
408
409         /**#@+
410          * Validates if a tag value is of the type it should be according to the Exif spec
411          *
412          * @private
413          *
414          * @param $in Mixed: the input value to check
415          * @return bool
416          */
417         function isByte( $in ) {
418                 if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 255 ) {
419                         $this->debug( $in, __FUNCTION__, true );
420                         return true;
421                 } else {
422                         $this->debug( $in, __FUNCTION__, false );
423                         return false;
424                 }
425         }
426
427         function isASCII( $in ) {
428                 if ( is_array( $in ) ) {
429                         return false;
430                 }
431
432                 if ( preg_match( "/[^\x0a\x20-\x7e]/", $in ) ) {
433                         $this->debug( $in, __FUNCTION__, 'found a character not in our whitelist' );
434                         return false;
435                 }
436
437                 if ( preg_match( '/^\s*$/', $in ) ) {
438                         $this->debug( $in, __FUNCTION__, 'input consisted solely of whitespace' );
439                         return false;
440                 }
441
442                 return true;
443         }
444
445         function isShort( $in ) {
446                 if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 65536 ) {
447                         $this->debug( $in, __FUNCTION__, true );
448                         return true;
449                 } else {
450                         $this->debug( $in, __FUNCTION__, false );
451                         return false;
452                 }
453         }
454
455         function isLong( $in ) {
456                 if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 4294967296 ) {
457                         $this->debug( $in, __FUNCTION__, true );
458                         return true;
459                 } else {
460                         $this->debug( $in, __FUNCTION__, false );
461                         return false;
462                 }
463         }
464
465         function isRational( $in ) {
466                 $m = array();
467                 if ( !is_array( $in ) && @preg_match( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero
468                         return $this->isLong( $m[1] ) && $this->isLong( $m[2] );
469                 } else {
470                         $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
471                         return false;
472                 }
473         }
474
475         function isUndefined( $in ) {
476                 if ( !is_array( $in ) && preg_match( '/^\d{4}$/', $in ) ) { // Allow ExifVersion and FlashpixVersion
477                         $this->debug( $in, __FUNCTION__, true );
478                         return true;
479                 } else {
480                         $this->debug( $in, __FUNCTION__, false );
481                         return false;
482                 }
483         }
484
485         function isSlong( $in ) {
486                 if ( $this->isLong( abs( $in ) ) ) {
487                         $this->debug( $in, __FUNCTION__, true );
488                         return true;
489                 } else {
490                         $this->debug( $in, __FUNCTION__, false );
491                         return false;
492                 }
493         }
494
495         function isSrational( $in ) {
496                 $m = array();
497                 if ( !is_array( $in ) && preg_match( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero
498                         return $this->isSlong( $m[0] ) && $this->isSlong( $m[1] );
499                 } else {
500                         $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
501                         return false;
502                 }
503         }
504         /**#@-*/
505
506         /**
507          * Validates if a tag has a legal value according to the Exif spec
508          *
509          * @private
510          *
511          * @param $tag String: the tag to check.
512          * @param $val Mixed: the value of the tag.
513          * @return bool
514          */
515         function validate( $tag, $val ) {
516                 $debug = "tag is '$tag'";
517                 // Does not work if not typecast
518                 switch( (string)$this->mFlatExifTags[$tag] ) {
519                         case (string)Exif::BYTE:
520                                 $this->debug( $val, __FUNCTION__, $debug );
521                                 return $this->isByte( $val );
522                         case (string)Exif::ASCII:
523                                 $this->debug( $val, __FUNCTION__, $debug );
524                                 return $this->isASCII( $val );
525                         case (string)Exif::SHORT:
526                                 $this->debug( $val, __FUNCTION__, $debug );
527                                 return $this->isShort( $val );
528                         case (string)Exif::LONG:
529                                 $this->debug( $val, __FUNCTION__, $debug );
530                                 return $this->isLong( $val );
531                         case (string)Exif::RATIONAL:
532                                 $this->debug( $val, __FUNCTION__, $debug );
533                                 return $this->isRational( $val );
534                         case (string)Exif::UNDEFINED:
535                                 $this->debug( $val, __FUNCTION__, $debug );
536                                 return $this->isUndefined( $val );
537                         case (string)Exif::SLONG:
538                                 $this->debug( $val, __FUNCTION__, $debug );
539                                 return $this->isSlong( $val );
540                         case (string)Exif::SRATIONAL:
541                                 $this->debug( $val, __FUNCTION__, $debug );
542                                 return $this->isSrational( $val );
543                         case (string)Exif::SHORT.','.Exif::LONG:
544                                 $this->debug( $val, __FUNCTION__, $debug );
545                                 return $this->isShort( $val ) || $this->isLong( $val );
546                         default:
547                                 $this->debug( $val, __FUNCTION__, "The tag '$tag' is unknown" );
548                                 return false;
549                 }
550         }
551
552         /**
553          * Convenience function for debugging output
554          *
555          * @private
556          *
557          * @param $in Mixed:
558          * @param $fname String:
559          * @param $action Mixed: , default NULL.
560          */
561         function debug( $in, $fname, $action = NULL ) {
562                 if ( !$this->log ) {
563                         return;
564                 }
565                 $type = gettype( $in );
566                 $class = ucfirst( __CLASS__ );
567                 if ( $type === 'array' )
568                         $in = print_r( $in, true );
569
570                 if ( $action === true )
571                         wfDebugLog( $this->log, "$class::$fname: accepted: '$in' (type: $type)\n");
572                 elseif ( $action === false )
573                         wfDebugLog( $this->log, "$class::$fname: rejected: '$in' (type: $type)\n");
574                 elseif ( $action === null )
575                         wfDebugLog( $this->log, "$class::$fname: input was: '$in' (type: $type)\n");
576                 else
577                         wfDebugLog( $this->log, "$class::$fname: $action (type: $type; content: '$in')\n");
578         }
579
580         /**
581          * Convenience function for debugging output
582          *
583          * @private
584          *
585          * @param $fname String: the name of the function calling this function
586          * @param $io Boolean: Specify whether we're beginning or ending
587          */
588         function debugFile( $fname, $io ) {
589                 if ( !$this->log ) {
590                         return;
591                 }
592                 $class = ucfirst( __CLASS__ );
593                 if ( $io ) {
594                         wfDebugLog( $this->log, "$class::$fname: begin processing: '{$this->basename}'\n" );
595                 } else {
596                         wfDebugLog( $this->log, "$class::$fname: end processing: '{$this->basename}'\n" );
597                 }
598         }
599
600 }
601
602 /**
603  * @todo document (e.g. one-sentence class-overview description)
604  * @ingroup Media
605  */
606 class FormatExif {
607         /**
608          * The Exif data to format
609          *
610          * @var array
611          * @private
612          */
613         var $mExif;
614
615         /**
616          * Constructor
617          *
618          * @param $exif Array: the Exif data to format ( as returned by
619          *                    Exif::getFilteredData() )
620          */
621         function FormatExif( $exif ) {
622                 $this->mExif = $exif;
623         }
624
625         /**
626          * Numbers given by Exif user agents are often magical, that is they
627          * should be replaced by a detailed explanation depending on their
628          * value which most of the time are plain integers. This function
629          * formats Exif values into human readable form.
630          *
631          * @return array
632          */
633         function getFormattedData() {
634                 global $wgLang;
635
636                 $tags =& $this->mExif;
637
638                 $resolutionunit = !isset( $tags['ResolutionUnit'] ) || $tags['ResolutionUnit'] == 2 ? 2 : 3;
639                 unset( $tags['ResolutionUnit'] );
640
641                 foreach( $tags as $tag => $val ) {
642                         switch( $tag ) {
643                         case 'Compression':
644                                 switch( $val ) {
645                                 case 1: case 6:
646                                         $tags[$tag] = $this->msg( $tag, $val );
647                                         break;
648                                 default:
649                                         $tags[$tag] = $val;
650                                         break;
651                                 }
652                                 break;
653
654                         case 'PhotometricInterpretation':
655                                 switch( $val ) {
656                                 case 2: case 6:
657                                         $tags[$tag] = $this->msg( $tag, $val );
658                                         break;
659                                 default:
660                                         $tags[$tag] = $val;
661                                         break;
662                                 }
663                                 break;
664
665                         case 'Orientation':
666                                 switch( $val ) {
667                                 case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
668                                         $tags[$tag] = $this->msg( $tag, $val );
669                                         break;
670                                 default:
671                                         $tags[$tag] = $val;
672                                         break;
673                                 }
674                                 break;
675
676                         case 'PlanarConfiguration':
677                                 switch( $val ) {
678                                 case 1: case 2:
679                                         $tags[$tag] = $this->msg( $tag, $val );
680                                         break;
681                                 default:
682                                         $tags[$tag] = $val;
683                                         break;
684                                 }
685                                 break;
686
687                         // TODO: YCbCrSubSampling
688                         // TODO: YCbCrPositioning
689
690                         case 'XResolution':
691                         case 'YResolution':
692                                 switch( $resolutionunit ) {
693                                         case 2:
694                                                 $tags[$tag] = $this->msg( 'XYResolution', 'i', $this->formatNum( $val ) );
695                                                 break;
696                                         case 3:
697                                                 $this->msg( 'XYResolution', 'c', $this->formatNum( $val ) );
698                                                 break;
699                                         default:
700                                                 $tags[$tag] = $val;
701                                                 break;
702                                 }
703                                 break;
704
705                         // TODO: YCbCrCoefficients  #p27 (see annex E)
706                         case 'ExifVersion': case 'FlashpixVersion':
707                                 $tags[$tag] = "$val"/100;
708                                 break;
709
710                         case 'ColorSpace':
711                                 switch( $val ) {
712                                 case 1: case 'FFFF.H':
713                                         $tags[$tag] = $this->msg( $tag, $val );
714                                         break;
715                                 default:
716                                         $tags[$tag] = $val;
717                                         break;
718                                 }
719                                 break;
720
721                         case 'ComponentsConfiguration':
722                                 switch( $val ) {
723                                 case 0: case 1: case 2: case 3: case 4: case 5: case 6:
724                                         $tags[$tag] = $this->msg( $tag, $val );
725                                         break;
726                                 default:
727                                         $tags[$tag] = $val;
728                                         break;
729                                 }
730                                 break;
731
732                         case 'DateTime':
733                         case 'DateTimeOriginal':
734                         case 'DateTimeDigitized':
735                                 if( $val == '0000:00:00 00:00:00' ) {
736                                         $tags[$tag] = wfMsg('exif-unknowndate');
737                                 } elseif( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/', $val ) ) {
738                                         $tags[$tag] = $wgLang->timeanddate( wfTimestamp(TS_MW, $val) );
739                                 }
740                                 break;
741
742                         case 'ExposureProgram':
743                                 switch( $val ) {
744                                 case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
745                                         $tags[$tag] = $this->msg( $tag, $val );
746                                         break;
747                                 default:
748                                         $tags[$tag] = $val;
749                                         break;
750                                 }
751                                 break;
752
753                         case 'SubjectDistance':
754                                 $tags[$tag] = $this->msg( $tag, '', $this->formatNum( $val ) );
755                                 break;
756
757                         case 'MeteringMode':
758                                 switch( $val ) {
759                                 case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 255:
760                                         $tags[$tag] = $this->msg( $tag, $val );
761                                         break;
762                                 default:
763                                         $tags[$tag] = $val;
764                                         break;
765                                 }
766                                 break;
767
768                         case 'LightSource':
769                                 switch( $val ) {
770                                 case 0: case 1: case 2: case 3: case 4: case 9: case 10: case 11:
771                                 case 12: case 13: case 14: case 15: case 17: case 18: case 19: case 20:
772                                 case 21: case 22: case 23: case 24: case 255:
773                                         $tags[$tag] = $this->msg( $tag, $val );
774                                         break;
775                                 default:
776                                         $tags[$tag] = $val;
777                                         break;
778                                 }
779                                 break;
780
781                         case 'Flash':
782                                 $flashDecode = array(
783                                         'fired'    => $val & bindec( '00000001' ),
784                                         'return'   => ($val & bindec( '00000110' )) >> 1,
785                                         'mode'     => ($val & bindec( '00011000' )) >> 3,
786                                         'function' => ($val & bindec( '00100000' )) >> 5,
787                                         'redeye'   => ($val & bindec( '01000000' )) >> 6,
788 //                                      'reserved' => ($val & bindec( '10000000' )) >> 7,
789                                 );
790
791                                 # We do not need to handle unknown values since all are used.
792                                 foreach( $flashDecode as $subTag => $subValue ) {
793                                         # We do not need any message for zeroed values.
794                                         if( $subTag != 'fired' && $subValue == 0) {
795                                                 continue;
796                                         }
797                                         $fullTag = $tag . '-' . $subTag ;
798                                         $flashMsgs[] = $this->msg( $fullTag, $subValue );
799                                 }
800                                 $tags[$tag] = $wgLang->commaList( $flashMsgs );
801                         break;
802
803                         case 'FocalPlaneResolutionUnit':
804                                 switch( $val ) {
805                                 case 2:
806                                         $tags[$tag] = $this->msg( $tag, $val );
807                                         break;
808                                 default:
809                                         $tags[$tag] = $val;
810                                         break;
811                                 }
812                                 break;
813
814                         case 'SensingMethod':
815                                 switch( $val ) {
816                                 case 1: case 2: case 3: case 4: case 5: case 7: case 8:
817                                         $tags[$tag] = $this->msg( $tag, $val );
818                                         break;
819                                 default:
820                                         $tags[$tag] = $val;
821                                         break;
822                                 }
823                                 break;
824
825                         case 'FileSource':
826                                 switch( $val ) {
827                                 case 3:
828                                         $tags[$tag] = $this->msg( $tag, $val );
829                                         break;
830                                 default:
831                                         $tags[$tag] = $val;
832                                         break;
833                                 }
834                                 break;
835
836                         case 'SceneType':
837                                 switch( $val ) {
838                                 case 1:
839                                         $tags[$tag] = $this->msg( $tag, $val );
840                                         break;
841                                 default:
842                                         $tags[$tag] = $val;
843                                         break;
844                                 }
845                                 break;
846
847                         case 'CustomRendered':
848                                 switch( $val ) {
849                                 case 0: case 1:
850                                         $tags[$tag] = $this->msg( $tag, $val );
851                                         break;
852                                 default:
853                                         $tags[$tag] = $val;
854                                         break;
855                                 }
856                                 break;
857
858                         case 'ExposureMode':
859                                 switch( $val ) {
860                                 case 0: case 1: case 2:
861                                         $tags[$tag] = $this->msg( $tag, $val );
862                                         break;
863                                 default:
864                                         $tags[$tag] = $val;
865                                         break;
866                                 }
867                                 break;
868
869                         case 'WhiteBalance':
870                                 switch( $val ) {
871                                 case 0: case 1:
872                                         $tags[$tag] = $this->msg( $tag, $val );
873                                         break;
874                                 default:
875                                         $tags[$tag] = $val;
876                                         break;
877                                 }
878                                 break;
879
880                         case 'SceneCaptureType':
881                                 switch( $val ) {
882                                 case 0: case 1: case 2: case 3:
883                                         $tags[$tag] = $this->msg( $tag, $val );
884                                         break;
885                                 default:
886                                         $tags[$tag] = $val;
887                                         break;
888                                 }
889                                 break;
890
891                         case 'GainControl':
892                                 switch( $val ) {
893                                 case 0: case 1: case 2: case 3: case 4:
894                                         $tags[$tag] = $this->msg( $tag, $val );
895                                         break;
896                                 default:
897                                         $tags[$tag] = $val;
898                                         break;
899                                 }
900                                 break;
901
902                         case 'Contrast':
903                                 switch( $val ) {
904                                 case 0: case 1: case 2:
905                                         $tags[$tag] = $this->msg( $tag, $val );
906                                         break;
907                                 default:
908                                         $tags[$tag] = $val;
909                                         break;
910                                 }
911                                 break;
912
913                         case 'Saturation':
914                                 switch( $val ) {
915                                 case 0: case 1: case 2:
916                                         $tags[$tag] = $this->msg( $tag, $val );
917                                         break;
918                                 default:
919                                         $tags[$tag] = $val;
920                                         break;
921                                 }
922                                 break;
923
924                         case 'Sharpness':
925                                 switch( $val ) {
926                                 case 0: case 1: case 2:
927                                         $tags[$tag] = $this->msg( $tag, $val );
928                                         break;
929                                 default:
930                                         $tags[$tag] = $val;
931                                         break;
932                                 }
933                                 break;
934
935                         case 'SubjectDistanceRange':
936                                 switch( $val ) {
937                                 case 0: case 1: case 2: case 3:
938                                         $tags[$tag] = $this->msg( $tag, $val );
939                                         break;
940                                 default:
941                                         $tags[$tag] = $val;
942                                         break;
943                                 }
944                                 break;
945
946                         case 'GPSLatitudeRef':
947                         case 'GPSDestLatitudeRef':
948                                 switch( $val ) {
949                                 case 'N': case 'S':
950                                         $tags[$tag] = $this->msg( 'GPSLatitude', $val );
951                                         break;
952                                 default:
953                                         $tags[$tag] = $val;
954                                         break;
955                                 }
956                                 break;
957
958                         case 'GPSLongitudeRef':
959                         case 'GPSDestLongitudeRef':
960                                 switch( $val ) {
961                                 case 'E': case 'W':
962                                         $tags[$tag] = $this->msg( 'GPSLongitude', $val );
963                                         break;
964                                 default:
965                                         $tags[$tag] = $val;
966                                         break;
967                                 }
968                                 break;
969
970                         case 'GPSStatus':
971                                 switch( $val ) {
972                                 case 'A': case 'V':
973                                         $tags[$tag] = $this->msg( $tag, $val );
974                                         break;
975                                 default:
976                                         $tags[$tag] = $val;
977                                         break;
978                                 }
979                                 break;
980
981                         case 'GPSMeasureMode':
982                                 switch( $val ) {
983                                 case 2: case 3:
984                                         $tags[$tag] = $this->msg( $tag, $val );
985                                         break;
986                                 default:
987                                         $tags[$tag] = $val;
988                                         break;
989                                 }
990                                 break;
991
992                         case 'GPSSpeedRef':
993                         case 'GPSDestDistanceRef':
994                                 switch( $val ) {
995                                 case 'K': case 'M': case 'N':
996                                         $tags[$tag] = $this->msg( 'GPSSpeed', $val );
997                                         break;
998                                 default:
999                                         $tags[$tag] = $val;
1000                                         break;
1001                                 }
1002                                 break;
1003
1004                         case 'GPSTrackRef':
1005                         case 'GPSImgDirectionRef':
1006                         case 'GPSDestBearingRef':
1007                                 switch( $val ) {
1008                                 case 'T': case 'M':
1009                                         $tags[$tag] = $this->msg( 'GPSDirection', $val );
1010                                         break;
1011                                 default:
1012                                         $tags[$tag] = $val;
1013                                         break;
1014                                 }
1015                                 break;
1016
1017                         case 'GPSDateStamp':
1018                                 $tags[$tag] = $wgLang->date( substr( $val, 0, 4 ) . substr( $val, 5, 2 ) . substr( $val, 8, 2 ) . '000000' );
1019                                 break;
1020
1021                         // This is not in the Exif standard, just a special
1022                         // case for our purposes which enables wikis to wikify
1023                         // the make, model and software name to link to their articles.
1024                         case 'Make':
1025                         case 'Model':
1026                         case 'Software':
1027                                 $tags[$tag] = $this->msg( $tag, '', $val );
1028                                 break;
1029
1030                         case 'ExposureTime':
1031                                 // Show the pretty fraction as well as decimal version
1032                                 $tags[$tag] = wfMsg( 'exif-exposuretime-format',
1033                                         $this->formatFraction( $val ), $this->formatNum( $val ) );
1034                                 break;
1035
1036                         case 'FNumber':
1037                                 $tags[$tag] = wfMsg( 'exif-fnumber-format',
1038                                         $this->formatNum( $val ) );
1039                                 break;
1040
1041                         case 'FocalLength':
1042                                 $tags[$tag] = wfMsg( 'exif-focallength-format',
1043                                         $this->formatNum( $val ) );
1044                                 break;
1045
1046                         default:
1047                                 $tags[$tag] = $this->formatNum( $val );
1048                                 break;
1049                         }
1050                 }
1051
1052                 return $tags;
1053         }
1054
1055         /**
1056          * Convenience function for getFormattedData()
1057          *
1058          * @private
1059          *
1060          * @param $tag String: the tag name to pass on
1061          * @param $val String: the value of the tag
1062          * @param $arg String: an argument to pass ($1)
1063          * @return string A wfMsg of "exif-$tag-$val" in lower case
1064          */
1065         function msg( $tag, $val, $arg = null ) {
1066                 global $wgContLang;
1067
1068                 if ($val === '')
1069                         $val = 'value';
1070                 return wfMsg( $wgContLang->lc( "exif-$tag-$val" ), $arg );
1071         }
1072
1073         /**
1074          * Format a number, convert numbers from fractions into floating point
1075          * numbers
1076          *
1077          * @private
1078          *
1079          * @param $num Mixed: the value to format
1080          * @return mixed A floating point number or whatever we were fed
1081          */
1082         function formatNum( $num ) {
1083                 $m = array();
1084                 if ( preg_match( '/^(\d+)\/(\d+)$/', $num, $m ) )
1085                         return $m[2] != 0 ? $m[1] / $m[2] : $num;
1086                 else
1087                         return $num;
1088         }
1089
1090         /**
1091          * Format a rational number, reducing fractions
1092          *
1093          * @private
1094          *
1095          * @param $num Mixed: the value to format
1096          * @return mixed A floating point number or whatever we were fed
1097          */
1098         function formatFraction( $num ) {
1099                 $m = array();
1100                 if ( preg_match( '/^(\d+)\/(\d+)$/', $num, $m ) ) {
1101                         $numerator = intval( $m[1] );
1102                         $denominator = intval( $m[2] );
1103                         $gcd = $this->gcd( $numerator, $denominator );
1104                         if( $gcd != 0 ) {
1105                                 // 0 shouldn't happen! ;)
1106                                 return $numerator / $gcd . '/' . $denominator / $gcd;
1107                         }
1108                 }
1109                 return $this->formatNum( $num );
1110         }
1111
1112         /**
1113          * Calculate the greatest common divisor of two integers.
1114          *
1115          * @param $a Integer: FIXME
1116          * @param $b Integer: FIXME
1117          * @return int
1118          * @private
1119          */
1120         function gcd( $a, $b ) {
1121                 /*
1122                         // http://en.wikipedia.org/wiki/Euclidean_algorithm
1123                         // Recursive form would be:
1124                         if( $b == 0 )
1125                                 return $a;
1126                         else
1127                                 return gcd( $b, $a % $b );
1128                 */
1129                 while( $b != 0 ) {
1130                         $remainder = $a % $b;
1131
1132                         // tail recursion...
1133                         $a = $b;
1134                         $b = $remainder;
1135                 }
1136                 return $a;
1137         }
1138 }
1139
1140 /**
1141  * MW 1.6 compatibility
1142  */
1143 define( 'MW_EXIF_BYTE', Exif::BYTE );
1144 define( 'MW_EXIF_ASCII', Exif::ASCII );
1145 define( 'MW_EXIF_SHORT', Exif::SHORT );
1146 define( 'MW_EXIF_LONG', Exif::LONG );
1147 define( 'MW_EXIF_RATIONAL', Exif::RATIONAL );
1148 define( 'MW_EXIF_UNDEFINED', Exif::UNDEFINED );
1149 define( 'MW_EXIF_SLONG', Exif::SLONG );
1150 define( 'MW_EXIF_SRATIONAL', Exif::SRATIONAL );