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