MediaWiki  REL1_20
Exif.php
Go to the documentation of this file.
00001 <?php
00032 class Exif {
00033 
00034         const BYTE      = 1;    
00035         const ASCII     = 2;    
00036         const SHORT     = 3;    
00037         const LONG      = 4;    
00038         const RATIONAL  = 5;    
00039         const UNDEFINED = 7;    
00040         const SLONG     = 9;    
00041         const SRATIONAL = 10;   
00042         const IGNORE    = -1;   // A fake value for things we don't want or don't support.
00043 
00045         /* @var array
00046          * @private
00047          */
00048 
00054         var $mExifTags;
00055 
00059         var $mRawExifData;
00060 
00066         var $mFilteredExifData;
00067 
00071         var $mFormattedExifData;
00072 
00074 
00076         /* @var string
00077          * @private
00078          */
00079 
00083         var $file;
00084 
00088         var $basename;
00089 
00093         var $log = false;
00094 
00099         private $byteOrder;
00101 
00113         function __construct( $file, $byteOrder = '' ) {
00122                 $this->mExifTags = array(
00123                         # TIFF Rev. 6.0 Attribute Information (p22)
00124                         'IFD0' => array(
00125                                 # Tags relating to image structure
00126                                 'ImageWidth' => Exif::SHORT.','.Exif::LONG,             # Image width
00127                                 'ImageLength' => Exif::SHORT.','.Exif::LONG,            # Image height
00128                                 'BitsPerSample' => array( Exif::SHORT, 3 ),             # Number of bits per component
00129                                 # "When a primary image is JPEG compressed, this designation is not"
00130                                 # "necessary and is omitted." (p23)
00131                                 'Compression' => Exif::SHORT,                           # Compression scheme #p23
00132                                 'PhotometricInterpretation' => Exif::SHORT,             # Pixel composition #p23
00133                                 'Orientation' => Exif::SHORT,                           # Orientation of image #p24
00134                                 'SamplesPerPixel' => Exif::SHORT,                       # Number of components
00135                                 'PlanarConfiguration' => Exif::SHORT,                   # Image data arrangement #p24
00136                                 'YCbCrSubSampling' => array( Exif::SHORT, 2),           # Subsampling ratio of Y to C #p24
00137                                 'YCbCrPositioning' => Exif::SHORT,                      # Y and C positioning #p24-25
00138                                 'XResolution' => Exif::RATIONAL,                        # Image resolution in width direction
00139                                 'YResolution' => Exif::RATIONAL,                        # Image resolution in height direction
00140                                 'ResolutionUnit' => Exif::SHORT,                        # Unit of X and Y resolution #(p26)
00141 
00142                                 # Tags relating to recording offset
00143                                 'StripOffsets' => Exif::SHORT.','.Exif::LONG,                   # Image data location
00144                                 'RowsPerStrip' => Exif::SHORT.','.Exif::LONG,                   # Number of rows per strip
00145                                 'StripByteCounts' => Exif::SHORT.','.Exif::LONG,                # Bytes per compressed strip
00146                                 'JPEGInterchangeFormat' => Exif::SHORT.','.Exif::LONG,          # Offset to JPEG SOI
00147                                 'JPEGInterchangeFormatLength' => Exif::SHORT.','.Exif::LONG,    # Bytes of JPEG data
00148 
00149                                 # Tags relating to image data characteristics
00150                                 'TransferFunction' => Exif::IGNORE,                     # Transfer function
00151                                 'WhitePoint' => array( Exif::RATIONAL, 2),              # White point chromaticity
00152                                 'PrimaryChromaticities' => array( Exif::RATIONAL, 6),   # Chromaticities of primarities
00153                                 'YCbCrCoefficients' => array( Exif::RATIONAL, 3),       # Color space transformation matrix coefficients #p27
00154                                 'ReferenceBlackWhite' => array( Exif::RATIONAL, 6),     # Pair of black and white reference values
00155 
00156                                 # Other tags
00157                                 'DateTime' => Exif::ASCII,                              # File change date and time
00158                                 'ImageDescription' => Exif::ASCII,                      # Image title
00159                                 'Make' => Exif::ASCII,                                  # Image input equipment manufacturer
00160                                 'Model' => Exif::ASCII,                                 # Image input equipment model
00161                                 'Software' => Exif::ASCII,                              # Software used
00162                                 'Artist' => Exif::ASCII,                                # Person who created the image
00163                                 'Copyright' => Exif::ASCII,                             # Copyright holder
00164                         ),
00165 
00166                         # Exif IFD Attribute Information (p30-31)
00167                         'EXIF' => array(
00168                                 # TODO: NOTE: Nonexistence of this field is taken to mean nonconformance
00169                                 # to the EXIF 2.1 AND 2.2 standards
00170                                 'ExifVersion' =>  Exif::UNDEFINED,                      # Exif version
00171                                 'FlashPixVersion' => Exif::UNDEFINED,                   # Supported Flashpix version #p32
00172 
00173                                 # Tags relating to Image Data Characteristics
00174                                 'ColorSpace' => Exif::SHORT,                            # Color space information #p32
00175 
00176                                 # Tags relating to image configuration
00177                                 'ComponentsConfiguration' => Exif::UNDEFINED,                   # Meaning of each component #p33
00178                                 'CompressedBitsPerPixel' => Exif::RATIONAL,                     # Image compression mode
00179                                 'PixelYDimension' => Exif::SHORT.','.Exif::LONG,                # Valid image width
00180                                 'PixelXDimension' => Exif::SHORT.','.Exif::LONG,                # Valid image height
00181 
00182                                 # Tags relating to related user information
00183                                 'MakerNote' => Exif::IGNORE,                            # Manufacturer notes
00184                                 'UserComment' => Exif::UNDEFINED,                       # User comments #p34
00185 
00186                                 # Tags relating to related file information
00187                                 'RelatedSoundFile' => Exif::ASCII,                      # Related audio file
00188 
00189                                 # Tags relating to date and time
00190                                 'DateTimeOriginal' => Exif::ASCII,                      # Date and time of original data generation #p36
00191                                 'DateTimeDigitized' => Exif::ASCII,                     # Date and time of original data generation
00192                                 'SubSecTime' => Exif::ASCII,                            # DateTime subseconds
00193                                 'SubSecTimeOriginal' => Exif::ASCII,                    # DateTimeOriginal subseconds
00194                                 'SubSecTimeDigitized' => Exif::ASCII,                   # DateTimeDigitized subseconds
00195 
00196                                 # Tags relating to picture-taking conditions (p31)
00197                                 'ExposureTime' => Exif::RATIONAL,                       # Exposure time
00198                                 'FNumber' => Exif::RATIONAL,                            # F Number
00199                                 'ExposureProgram' => Exif::SHORT,                       # Exposure Program #p38
00200                                 'SpectralSensitivity' => Exif::ASCII,                   # Spectral sensitivity
00201                                 'ISOSpeedRatings' => Exif::SHORT,                       # ISO speed rating
00202                                 'OECF' => Exif::IGNORE,
00203                                 # Optoelectronic conversion factor. Note: We don't have support for this atm.
00204                                 'ShutterSpeedValue' => Exif::SRATIONAL,                 # Shutter speed
00205                                 'ApertureValue' => Exif::RATIONAL,                      # Aperture
00206                                 'BrightnessValue' => Exif::SRATIONAL,                   # Brightness
00207                                 'ExposureBiasValue' => Exif::SRATIONAL,                 # Exposure bias
00208                                 'MaxApertureValue' => Exif::RATIONAL,                   # Maximum land aperture
00209                                 'SubjectDistance' => Exif::RATIONAL,                    # Subject distance
00210                                 'MeteringMode' => Exif::SHORT,                          # Metering mode #p40
00211                                 'LightSource' => Exif::SHORT,                           # Light source #p40-41
00212                                 'Flash' => Exif::SHORT,                                 # Flash #p41-42
00213                                 'FocalLength' => Exif::RATIONAL,                        # Lens focal length
00214                                 'SubjectArea' => array( Exif::SHORT, 4 ),               # Subject area
00215                                 'FlashEnergy' => Exif::RATIONAL,                        # Flash energy
00216                                 'SpatialFrequencyResponse' => Exif::IGNORE,             # Spatial frequency response. Not supported atm.
00217                                 'FocalPlaneXResolution' => Exif::RATIONAL,              # Focal plane X resolution
00218                                 'FocalPlaneYResolution' => Exif::RATIONAL,              # Focal plane Y resolution
00219                                 'FocalPlaneResolutionUnit' => Exif::SHORT,              # Focal plane resolution unit #p46
00220                                 'SubjectLocation' => array( Exif::SHORT, 2),            # Subject location
00221                                 'ExposureIndex' => Exif::RATIONAL,                      # Exposure index
00222                                 'SensingMethod' => Exif::SHORT,                         # Sensing method #p46
00223                                 'FileSource' => Exif::UNDEFINED,                        # File source #p47
00224                                 'SceneType' => Exif::UNDEFINED,                         # Scene type #p47
00225                                 'CFAPattern' => Exif::IGNORE,                           # CFA pattern. not supported atm.
00226                                 'CustomRendered' => Exif::SHORT,                        # Custom image processing #p48
00227                                 'ExposureMode' => Exif::SHORT,                          # Exposure mode #p48
00228                                 'WhiteBalance' => Exif::SHORT,                          # White Balance #p49
00229                                 'DigitalZoomRatio' => Exif::RATIONAL,                   # Digital zoom ration
00230                                 'FocalLengthIn35mmFilm' => Exif::SHORT,                 # Focal length in 35 mm film
00231                                 'SceneCaptureType' => Exif::SHORT,                      # Scene capture type #p49
00232                                 'GainControl' => Exif::SHORT,                           # Scene control #p49-50
00233                                 'Contrast' => Exif::SHORT,                              # Contrast #p50
00234                                 'Saturation' => Exif::SHORT,                            # Saturation #p50
00235                                 'Sharpness' => Exif::SHORT,                             # Sharpness #p50
00236                                 'DeviceSettingDescription' => Exif::IGNORE,
00237                                 # Device settings description. This could maybe be supported. Need to find an
00238                                 # example file that uses this to see if it has stuff of interest in it.
00239                                 'SubjectDistanceRange' => Exif::SHORT,                  # Subject distance range #p51
00240 
00241                                 'ImageUniqueID' => Exif::ASCII,                         # Unique image ID
00242                         ),
00243 
00244                         # GPS Attribute Information (p52)
00245                         'GPS' => array(
00246                                 'GPSVersion' => Exif::UNDEFINED,
00247                                 # Should be an array of 4 Exif::BYTE's. However php treats it as an undefined
00248                                 # Note exif standard calls this GPSVersionID, but php doesn't like the id suffix
00249                                 'GPSLatitudeRef' => Exif::ASCII,                        # North or South Latitude #p52-53
00250                                 'GPSLatitude' => array( Exif::RATIONAL, 3 ),            # Latitude
00251                                 'GPSLongitudeRef' => Exif::ASCII,                       # East or West Longitude #p53
00252                                 'GPSLongitude' => array( Exif::RATIONAL, 3),            # Longitude
00253                                 'GPSAltitudeRef' => Exif::UNDEFINED,
00254                                 # Altitude reference. Note, the exif standard says this should be an EXIF::Byte,
00255                                 # but php seems to disagree.
00256                                 'GPSAltitude' => Exif::RATIONAL,                        # Altitude
00257                                 'GPSTimeStamp' => array( Exif::RATIONAL, 3),            # GPS time (atomic clock)
00258                                 'GPSSatellites' => Exif::ASCII,                         # Satellites used for measurement
00259                                 'GPSStatus' => Exif::ASCII,                             # Receiver status #p54
00260                                 'GPSMeasureMode' => Exif::ASCII,                        # Measurement mode #p54-55
00261                                 'GPSDOP' => Exif::RATIONAL,                             # Measurement precision
00262                                 'GPSSpeedRef' => Exif::ASCII,                           # Speed unit #p55
00263                                 'GPSSpeed' => Exif::RATIONAL,                           # Speed of GPS receiver
00264                                 'GPSTrackRef' => Exif::ASCII,                           # Reference for direction of movement #p55
00265                                 'GPSTrack' => Exif::RATIONAL,                           # Direction of movement
00266                                 'GPSImgDirectionRef' => Exif::ASCII,                    # Reference for direction of image #p56
00267                                 'GPSImgDirection' => Exif::RATIONAL,                    # Direction of image
00268                                 'GPSMapDatum' => Exif::ASCII,                           # Geodetic survey data used
00269                                 'GPSDestLatitudeRef' => Exif::ASCII,                    # Reference for latitude of destination #p56
00270                                 'GPSDestLatitude' => array( Exif::RATIONAL, 3 ),        # Latitude destination
00271                                 'GPSDestLongitudeRef' => Exif::ASCII,                   # Reference for longitude of destination #p57
00272                                 'GPSDestLongitude' => array( Exif::RATIONAL, 3 ),       # Longitude of destination
00273                                 'GPSDestBearingRef' => Exif::ASCII,                     # Reference for bearing of destination #p57
00274                                 'GPSDestBearing' => Exif::RATIONAL,                     # Bearing of destination
00275                                 'GPSDestDistanceRef' => Exif::ASCII,                    # Reference for distance to destination #p57-58
00276                                 'GPSDestDistance' => Exif::RATIONAL,                    # Distance to destination
00277                                 'GPSProcessingMethod' => Exif::UNDEFINED,               # Name of GPS processing method
00278                                 'GPSAreaInformation' => Exif::UNDEFINED,                # Name of GPS area
00279                                 'GPSDateStamp' => Exif::ASCII,                          # GPS date
00280                                 'GPSDifferential' => Exif::SHORT,                       # GPS differential correction
00281                         ),
00282                 );
00283 
00284                 $this->file = $file;
00285                 $this->basename = wfBaseName( $this->file );
00286                 if ( $byteOrder === 'BE' || $byteOrder === 'LE' ) {
00287                         $this->byteOrder = $byteOrder;
00288                 } else {
00289                         // Only give a warning for b/c, since originally we didn't
00290                         // require this. The number of things affected by this is
00291                         // rather small.
00292                         wfWarn( 'Exif class did not have byte order specified. '
00293                          . 'Some properties may be decoded incorrectly.' );
00294                         $this->byteOrder = 'BE'; // BE seems about twice as popular as LE in jpg's.
00295                 }
00296 
00297                 $this->debugFile( $this->basename, __FUNCTION__, true );
00298                 if( function_exists( 'exif_read_data' ) ) {
00299                         wfSuppressWarnings();
00300                         $data = exif_read_data( $this->file, 0, true );
00301                         wfRestoreWarnings();
00302                 } else {
00303                         throw new MWException( "Internal error: exif_read_data not present. \$wgShowEXIF may be incorrectly set or not checked by an extension." );
00304                 }
00310                 $this->mRawExifData = $data ? $data : array();
00311                 $this->makeFilteredData();
00312                 $this->collapseData();
00313                 $this->debugFile( __FUNCTION__, false );
00314         }
00315 
00319         function makeFilteredData() {
00320                 $this->mFilteredExifData = Array();
00321 
00322                 foreach ( array_keys( $this->mRawExifData ) as $section ) {
00323                         if ( !in_array( $section, array_keys( $this->mExifTags ) ) ) {
00324                                 $this->debug( $section , __FUNCTION__, "'$section' is not a valid Exif section" );
00325                                 continue;
00326                         }
00327 
00328                         foreach ( array_keys( $this->mRawExifData[$section] ) as $tag ) {
00329                                 if ( !in_array( $tag, array_keys( $this->mExifTags[$section] ) ) ) {
00330                                         $this->debug( $tag, __FUNCTION__, "'$tag' is not a valid tag in '$section'" );
00331                                         continue;
00332                                 }
00333 
00334                                 $this->mFilteredExifData[$tag] = $this->mRawExifData[$section][$tag];
00335                                 // This is ok, as the tags in the different sections do not conflict.
00336                                 // except in computed and thumbnail section, which we don't use.
00337 
00338                                 $value = $this->mRawExifData[$section][$tag];
00339                                 if ( !$this->validate( $section, $tag, $value ) ) {
00340                                         $this->debug( $value, __FUNCTION__, "'$tag' contained invalid data" );
00341                                         unset( $this->mFilteredExifData[$tag] );
00342                                 }
00343                         }
00344                 }
00345         }
00346 
00365         function collapseData( ) {
00366 
00367                 $this->exifGPStoNumber( 'GPSLatitude' );
00368                 $this->exifGPStoNumber( 'GPSDestLatitude' );
00369                 $this->exifGPStoNumber( 'GPSLongitude' );
00370                 $this->exifGPStoNumber( 'GPSDestLongitude' );
00371 
00372                 if ( isset( $this->mFilteredExifData['GPSAltitude'] ) && isset( $this->mFilteredExifData['GPSAltitudeRef'] ) ) {
00373 
00374                         // We know altitude data is a <num>/<denom> from the validation functions ran earlier.
00375                         // But multiplying such a string by -1 doesn't work well, so convert.
00376                         list( $num, $denom ) = explode( '/', $this->mFilteredExifData['GPSAltitude'] );
00377                         $this->mFilteredExifData['GPSAltitude'] = $num / $denom;
00378 
00379                         if ( $this->mFilteredExifData['GPSAltitudeRef'] === "\1" ) {
00380                                 $this->mFilteredExifData['GPSAltitude'] *= - 1;
00381                         }
00382                         unset( $this->mFilteredExifData['GPSAltitudeRef'] );
00383                 }
00384 
00385                 $this->exifPropToOrd( 'FileSource' );
00386                 $this->exifPropToOrd( 'SceneType' );
00387 
00388                 $this->charCodeString( 'UserComment' );
00389                 $this->charCodeString( 'GPSProcessingMethod');
00390                 $this->charCodeString( 'GPSAreaInformation' );
00391                 
00392                 //ComponentsConfiguration should really be an array instead of a string...
00393                 //This turns a string of binary numbers into an array of numbers.
00394 
00395                 if ( isset ( $this->mFilteredExifData['ComponentsConfiguration'] ) ) {
00396                         $val = $this->mFilteredExifData['ComponentsConfiguration'];
00397                         $ccVals = array();
00398                         for ($i = 0; $i < strlen($val); $i++) {
00399                                 $ccVals[$i] = ord( substr($val, $i, 1) );
00400                         }
00401                         $ccVals['_type'] = 'ol'; //this is for formatting later.
00402                         $this->mFilteredExifData['ComponentsConfiguration'] = $ccVals;
00403                 }
00404         
00405                 //GPSVersion(ID) is treated as the wrong type by php exif support.
00406                 //Go through each byte turning it into a version string.
00407                 //For example: "\x02\x02\x00\x00" -> "2.2.0.0"
00408 
00409                 //Also change exif tag name from GPSVersion (what php exif thinks it is)
00410                 //to GPSVersionID (what the exif standard thinks it is).
00411 
00412                 if ( isset ( $this->mFilteredExifData['GPSVersion'] ) ) {
00413                         $val = $this->mFilteredExifData['GPSVersion'];
00414                         $newVal = '';
00415                         for ($i = 0; $i < strlen($val); $i++) {
00416                                 if ( $i !== 0 ) {
00417                                         $newVal .= '.';
00418                                 }
00419                                 $newVal .= ord( substr($val, $i, 1) );
00420                         }
00421                         if ( $this->byteOrder === 'LE' ) {
00422                                 // Need to reverse the string
00423                                 $newVal2 = '';
00424                                 for ( $i = strlen( $newVal ) - 1; $i >= 0; $i-- ) {
00425                                         $newVal2 .= substr( $newVal, $i, 1 );
00426                                 }
00427                                 $this->mFilteredExifData['GPSVersionID'] = $newVal2;
00428                         } else {
00429                                 $this->mFilteredExifData['GPSVersionID'] = $newVal;
00430                         }
00431                         unset( $this->mFilteredExifData['GPSVersion'] );
00432                 }
00433 
00434         }
00441         private function charCodeString ( $prop ) {
00442                 if ( isset( $this->mFilteredExifData[$prop] ) ) {
00443 
00444                         if ( strlen($this->mFilteredExifData[$prop]) <= 8 ) {
00445                                 //invalid. Must be at least 9 bytes long.
00446 
00447                                 $this->debug( $this->mFilteredExifData[$prop] , __FUNCTION__, false );
00448                                 unset($this->mFilteredExifData[$prop]);
00449                                 return;
00450                         }
00451                         $charCode = substr( $this->mFilteredExifData[$prop], 0, 8);
00452                         $val = substr( $this->mFilteredExifData[$prop], 8);
00453                         
00454                         
00455                         switch ($charCode) {
00456                                 case "\x4A\x49\x53\x00\x00\x00\x00\x00":
00457                                         //JIS
00458                                         $charset = "Shift-JIS";
00459                                         break;
00460                                 case "UNICODE\x00":
00461                                         $charset = "UTF-16" . $this->byteOrder;
00462                                         break;
00463                                 default: //ascii or undefined.
00464                                         $charset = "";
00465                                         break;
00466                         }
00467                         // This could possibly check to see if iconv is really installed
00468                         // or if we're using the compatibility wrapper in globalFunctions.php
00469                         if ($charset) {
00470                                 wfSuppressWarnings();
00471                                 $val = iconv($charset, 'UTF-8//IGNORE', $val);
00472                                 wfRestoreWarnings();
00473                         } else {
00474                                 // if valid utf-8, assume that, otherwise assume windows-1252
00475                                 $valCopy = $val;
00476                                 UtfNormal::quickIsNFCVerify( $valCopy ); //validates $valCopy.
00477                                 if ( $valCopy !== $val ) {
00478                                         wfSuppressWarnings();
00479                                         $val = iconv('Windows-1252', 'UTF-8//IGNORE', $val);
00480                                         wfRestoreWarnings();
00481                                 }
00482                         }
00483                         
00484                         //trim and check to make sure not only whitespace.
00485                         $val = trim($val);
00486                         if ( strlen( $val ) === 0 ) {
00487                                 //only whitespace.
00488                                 $this->debug( $this->mFilteredExifData[$prop] , __FUNCTION__, "$prop: Is only whitespace" );
00489                                 unset($this->mFilteredExifData[$prop]);
00490                                 return;
00491                         }
00492 
00493                         //all's good.
00494                         $this->mFilteredExifData[$prop] = $val;
00495                 }
00496         }
00503         private function exifPropToOrd ( $prop ) {
00504                 if ( isset( $this->mFilteredExifData[$prop] ) ) {
00505                         $this->mFilteredExifData[$prop] = ord( $this->mFilteredExifData[$prop] );
00506                 }
00507         }
00513         private function exifGPStoNumber ( $prop ) {
00514                 $loc =& $this->mFilteredExifData[$prop];
00515                 $dir =& $this->mFilteredExifData[$prop . 'Ref'];
00516                 $res = false;
00517 
00518                 if ( isset( $loc ) && isset( $dir ) && ( $dir === 'N' || $dir === 'S' || $dir === 'E' || $dir === 'W' ) ) {
00519                         list( $num, $denom ) = explode( '/', $loc[0] );
00520                         $res = $num / $denom;
00521                         list( $num, $denom ) = explode( '/', $loc[1] );
00522                         $res += ( $num / $denom ) * ( 1 / 60 );
00523                         list( $num, $denom ) = explode( '/', $loc[2] );
00524                         $res += ( $num / $denom ) * ( 1 / 3600 );
00525 
00526                         if ( $dir === 'S' || $dir === 'W' ) {
00527                                 $res *= - 1; // make negative
00528                         }
00529                 }
00530 
00531                 // update the exif records.
00532 
00533                 if ( $res !== false ) { // using !== as $res could potentially be 0
00534                         $this->mFilteredExifData[$prop] = $res;
00535                         unset( $this->mFilteredExifData[$prop . 'Ref'] );
00536                 } else { // if invalid
00537                         unset( $this->mFilteredExifData[$prop] );
00538                         unset( $this->mFilteredExifData[$prop . 'Ref'] );
00539                 }
00540         }
00541 
00548         function makeFormattedData( ) {
00549                 wfDeprecated( __METHOD__, '1.18' );
00550                 $this->mFormattedExifData = FormatMetadata::getFormattedData(
00551                         $this->mFilteredExifData );
00552         }
00562         function getData() {
00563                 return $this->mRawExifData;
00564         }
00565 
00569         function getFilteredData() {
00570                 return $this->mFilteredExifData;
00571         }
00572 
00581         function getFormattedData() {
00582                 wfDeprecated( __METHOD__, '1.18' );
00583                 if (!$this->mFormattedExifData) {
00584                         $this->makeFormattedData();
00585                 }
00586                 return $this->mFormattedExifData;
00587         }
00602         public static function version() {
00603                 return 2; // We don't need no bloddy constants!
00604         }
00605 
00614         private function isByte( $in ) {
00615                 if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 255 ) {
00616                         $this->debug( $in, __FUNCTION__, true );
00617                         return true;
00618                 } else {
00619                         $this->debug( $in, __FUNCTION__, false );
00620                         return false;
00621                 }
00622         }
00623 
00628         private function isASCII( $in ) {
00629                 if ( is_array( $in ) ) {
00630                         return false;
00631                 }
00632 
00633                 if ( preg_match( "/[^\x0a\x20-\x7e]/", $in ) ) {
00634                         $this->debug( $in, __FUNCTION__, 'found a character not in our whitelist' );
00635                         return false;
00636                 }
00637 
00638                 if ( preg_match( '/^\s*$/', $in ) ) {
00639                         $this->debug( $in, __FUNCTION__, 'input consisted solely of whitespace' );
00640                         return false;
00641                 }
00642 
00643                 return true;
00644         }
00645 
00650         private function isShort( $in ) {
00651                 if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 65536 ) {
00652                         $this->debug( $in, __FUNCTION__, true );
00653                         return true;
00654                 } else {
00655                         $this->debug( $in, __FUNCTION__, false );
00656                         return false;
00657                 }
00658         }
00659 
00664         private function isLong( $in ) {
00665                 if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 4294967296 ) {
00666                         $this->debug( $in, __FUNCTION__, true );
00667                         return true;
00668                 } else {
00669                         $this->debug( $in, __FUNCTION__, false );
00670                         return false;
00671                 }
00672         }
00673 
00678         private function isRational( $in ) {
00679                 $m = array();
00680                 if ( !is_array( $in ) && @preg_match( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero
00681                         return $this->isLong( $m[1] ) && $this->isLong( $m[2] );
00682                 } else {
00683                         $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
00684                         return false;
00685                 }
00686         }
00687 
00692         private function isUndefined( $in ) {
00693                 $this->debug( $in, __FUNCTION__, true );
00694                 return true;
00695         }
00696 
00701         private function isSlong( $in ) {
00702                 if ( $this->isLong( abs( $in ) ) ) {
00703                         $this->debug( $in, __FUNCTION__, true );
00704                         return true;
00705                 } else {
00706                         $this->debug( $in, __FUNCTION__, false );
00707                         return false;
00708                 }
00709         }
00710 
00715         private function isSrational( $in ) {
00716                 $m = array();
00717                 if ( !is_array( $in ) && preg_match( '/^(-?\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero
00718                         return $this->isSlong( $m[0] ) && $this->isSlong( $m[1] );
00719                 } else {
00720                         $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
00721                         return false;
00722                 }
00723         }
00736         private function validate( $section, $tag, $val, $recursive = false ) {
00737                 $debug = "tag is '$tag'";
00738                 $etype = $this->mExifTags[$section][$tag];
00739                 $ecount = 1;
00740                 if( is_array( $etype ) ) {
00741                         list( $etype, $ecount ) = $etype;
00742                         if ( $recursive )
00743                                 $ecount = 1; // checking individual elements
00744                 }
00745                 $count = count( $val );
00746                 if( $ecount != $count ) {
00747                         $this->debug( $val, __FUNCTION__, "Expected $ecount elements for $tag but got $count" );
00748                         return false;
00749                 }
00750                 if( $count > 1 ) {
00751                         foreach( $val as $v ) { 
00752                                 if( !$this->validate( $section, $tag, $v, true ) ) {
00753                                         return false; 
00754                                 } 
00755                         }
00756                         return true;
00757                 }
00758                 // Does not work if not typecast
00759                 switch( (string)$etype ) {
00760                         case (string)Exif::BYTE:
00761                                 $this->debug( $val, __FUNCTION__, $debug );
00762                                 return $this->isByte( $val );
00763                         case (string)Exif::ASCII:
00764                                 $this->debug( $val, __FUNCTION__, $debug );
00765                                 return $this->isASCII( $val );
00766                         case (string)Exif::SHORT:
00767                                 $this->debug( $val, __FUNCTION__, $debug );
00768                                 return $this->isShort( $val );
00769                         case (string)Exif::LONG:
00770                                 $this->debug( $val, __FUNCTION__, $debug );
00771                                 return $this->isLong( $val );
00772                         case (string)Exif::RATIONAL:
00773                                 $this->debug( $val, __FUNCTION__, $debug );
00774                                 return $this->isRational( $val );
00775                         case (string)Exif::UNDEFINED:
00776                                 $this->debug( $val, __FUNCTION__, $debug );
00777                                 return $this->isUndefined( $val );
00778                         case (string)Exif::SLONG:
00779                                 $this->debug( $val, __FUNCTION__, $debug );
00780                                 return $this->isSlong( $val );
00781                         case (string)Exif::SRATIONAL:
00782                                 $this->debug( $val, __FUNCTION__, $debug );
00783                                 return $this->isSrational( $val );
00784                         case (string)Exif::SHORT.','.Exif::LONG:
00785                                 $this->debug( $val, __FUNCTION__, $debug );
00786                                 return $this->isShort( $val ) || $this->isLong( $val );
00787                         case (string)Exif::IGNORE:
00788                                 $this->debug( $val, __FUNCTION__, $debug );
00789                                 return false;
00790                         default:
00791                                 $this->debug( $val, __FUNCTION__, "The tag '$tag' is unknown" );
00792                                 return false;
00793                 }
00794         }
00795 
00805         private function debug( $in, $fname, $action = null ) {
00806                 if ( !$this->log ) {
00807                         return;
00808                 }
00809                 $type = gettype( $in );
00810                 $class = ucfirst( __CLASS__ );
00811                 if ( $type === 'array' ) {
00812                         $in = print_r( $in, true );
00813                 }
00814 
00815                 if ( $action === true ) {
00816                         wfDebugLog( $this->log, "$class::$fname: accepted: '$in' (type: $type)\n");
00817                 } elseif ( $action === false ) {
00818                         wfDebugLog( $this->log, "$class::$fname: rejected: '$in' (type: $type)\n");
00819                 } elseif ( $action === null ) {
00820                         wfDebugLog( $this->log, "$class::$fname: input was: '$in' (type: $type)\n");
00821                 } else {
00822                         wfDebugLog( $this->log, "$class::$fname: $action (type: $type; content: '$in')\n");
00823                 }
00824         }
00825 
00834         private function debugFile( $fname, $io ) {
00835                 if ( !$this->log ) {
00836                         return;
00837                 }
00838                 $class = ucfirst( __CLASS__ );
00839                 if ( $io ) {
00840                         wfDebugLog( $this->log, "$class::$fname: begin processing: '{$this->basename}'\n" );
00841                 } else {
00842                         wfDebugLog( $this->log, "$class::$fname: end processing: '{$this->basename}'\n" );
00843                 }
00844         }
00845 }
00846