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