MediaWiki  REL1_23
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             // This could possibly check to see if iconv is really installed
00474             // or if we're using the compatibility wrapper in globalFunctions.php
00475             if ( $charset ) {
00476                 wfSuppressWarnings();
00477                 $val = iconv( $charset, 'UTF-8//IGNORE', $val );
00478                 wfRestoreWarnings();
00479             } else {
00480                 // if valid utf-8, assume that, otherwise assume windows-1252
00481                 $valCopy = $val;
00482                 UtfNormal::quickIsNFCVerify( $valCopy ); //validates $valCopy.
00483                 if ( $valCopy !== $val ) {
00484                     wfSuppressWarnings();
00485                     $val = iconv( 'Windows-1252', 'UTF-8//IGNORE', $val );
00486                     wfRestoreWarnings();
00487                 }
00488             }
00489 
00490             //trim and check to make sure not only whitespace.
00491             $val = trim( $val );
00492             if ( strlen( $val ) === 0 ) {
00493                 //only whitespace.
00494                 $this->debug( $this->mFilteredExifData[$prop], __FUNCTION__, "$prop: Is only whitespace" );
00495                 unset( $this->mFilteredExifData[$prop] );
00496 
00497                 return;
00498             }
00499 
00500             //all's good.
00501             $this->mFilteredExifData[$prop] = $val;
00502         }
00503     }
00504 
00511     private function exifPropToOrd( $prop ) {
00512         if ( isset( $this->mFilteredExifData[$prop] ) ) {
00513             $this->mFilteredExifData[$prop] = ord( $this->mFilteredExifData[$prop] );
00514         }
00515     }
00516 
00522     private function exifGPStoNumber( $prop ) {
00523         $loc =& $this->mFilteredExifData[$prop];
00524         $dir =& $this->mFilteredExifData[$prop . 'Ref'];
00525         $res = false;
00526 
00527         if ( isset( $loc ) && isset( $dir )
00528             && ( $dir === 'N' || $dir === 'S' || $dir === 'E' || $dir === 'W' )
00529         ) {
00530             list( $num, $denom ) = explode( '/', $loc[0] );
00531             $res = $num / $denom;
00532             list( $num, $denom ) = explode( '/', $loc[1] );
00533             $res += ( $num / $denom ) * ( 1 / 60 );
00534             list( $num, $denom ) = explode( '/', $loc[2] );
00535             $res += ( $num / $denom ) * ( 1 / 3600 );
00536 
00537             if ( $dir === 'S' || $dir === 'W' ) {
00538                 $res *= -1; // make negative
00539             }
00540         }
00541 
00542         // update the exif records.
00543 
00544         if ( $res !== false ) { // using !== as $res could potentially be 0
00545             $this->mFilteredExifData[$prop] = $res;
00546             unset( $this->mFilteredExifData[$prop . 'Ref'] );
00547         } else { // if invalid
00548             unset( $this->mFilteredExifData[$prop] );
00549             unset( $this->mFilteredExifData[$prop . 'Ref'] );
00550         }
00551     }
00552 
00562     function getData() {
00563         return $this->mRawExifData;
00564     }
00565 
00569     function getFilteredData() {
00570         return $this->mFilteredExifData;
00571     }
00572 
00587     public static function version() {
00588         return 2; // We don't need no bloddy constants!
00589     }
00590 
00597     private function isByte( $in ) {
00598         if ( !is_array( $in ) && sprintf( '%d', $in ) == $in && $in >= 0 && $in <= 255 ) {
00599             $this->debug( $in, __FUNCTION__, true );
00600 
00601             return true;
00602         } else {
00603             $this->debug( $in, __FUNCTION__, false );
00604 
00605             return false;
00606         }
00607     }
00608 
00613     private function isASCII( $in ) {
00614         if ( is_array( $in ) ) {
00615             return false;
00616         }
00617 
00618         if ( preg_match( "/[^\x0a\x20-\x7e]/", $in ) ) {
00619             $this->debug( $in, __FUNCTION__, 'found a character not in our whitelist' );
00620 
00621             return false;
00622         }
00623 
00624         if ( preg_match( '/^\s*$/', $in ) ) {
00625             $this->debug( $in, __FUNCTION__, 'input consisted solely of whitespace' );
00626 
00627             return false;
00628         }
00629 
00630         return true;
00631     }
00632 
00637     private function isShort( $in ) {
00638         if ( !is_array( $in ) && sprintf( '%d', $in ) == $in && $in >= 0 && $in <= 65536 ) {
00639             $this->debug( $in, __FUNCTION__, true );
00640 
00641             return true;
00642         } else {
00643             $this->debug( $in, __FUNCTION__, false );
00644 
00645             return false;
00646         }
00647     }
00648 
00653     private function isLong( $in ) {
00654         if ( !is_array( $in ) && sprintf( '%d', $in ) == $in && $in >= 0 && $in <= 4294967296 ) {
00655             $this->debug( $in, __FUNCTION__, true );
00656 
00657             return true;
00658         } else {
00659             $this->debug( $in, __FUNCTION__, false );
00660 
00661             return false;
00662         }
00663     }
00664 
00669     private function isRational( $in ) {
00670         $m = array();
00671 
00672         # Avoid division by zero
00673         if ( !is_array( $in )
00674             && preg_match( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m )
00675         ) {
00676             return $this->isLong( $m[1] ) && $this->isLong( $m[2] );
00677         } else {
00678             $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
00679 
00680             return false;
00681         }
00682     }
00683 
00688     private function isUndefined( $in ) {
00689         $this->debug( $in, __FUNCTION__, true );
00690 
00691         return true;
00692     }
00693 
00698     private function isSlong( $in ) {
00699         if ( $this->isLong( abs( $in ) ) ) {
00700             $this->debug( $in, __FUNCTION__, true );
00701 
00702             return true;
00703         } else {
00704             $this->debug( $in, __FUNCTION__, false );
00705 
00706             return false;
00707         }
00708     }
00709 
00714     private function isSrational( $in ) {
00715         $m = array();
00716 
00717         # Avoid division by zero
00718         if ( !is_array( $in ) &&
00719             preg_match( '/^(-?\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m )
00720         ) {
00721             return $this->isSlong( $m[0] ) && $this->isSlong( $m[1] );
00722         } else {
00723             $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
00724 
00725             return false;
00726         }
00727     }
00728 
00740     private function validate( $section, $tag, $val, $recursive = false ) {
00741         $debug = "tag is '$tag'";
00742         $etype = $this->mExifTags[$section][$tag];
00743         $ecount = 1;
00744         if ( is_array( $etype ) ) {
00745             list( $etype, $ecount ) = $etype;
00746             if ( $recursive ) {
00747                 $ecount = 1; // checking individual elements
00748             }
00749         }
00750         $count = count( $val );
00751         if ( $ecount != $count ) {
00752             $this->debug( $val, __FUNCTION__, "Expected $ecount elements for $tag but got $count" );
00753 
00754             return false;
00755         }
00756         if ( $count > 1 ) {
00757             foreach ( $val as $v ) {
00758                 if ( !$this->validate( $section, $tag, $v, true ) ) {
00759                     return false;
00760                 }
00761             }
00762 
00763             return true;
00764         }
00765         // Does not work if not typecast
00766         switch ( (string)$etype ) {
00767             case (string)Exif::BYTE:
00768                 $this->debug( $val, __FUNCTION__, $debug );
00769 
00770                 return $this->isByte( $val );
00771             case (string)Exif::ASCII:
00772                 $this->debug( $val, __FUNCTION__, $debug );
00773 
00774                 return $this->isASCII( $val );
00775             case (string)Exif::SHORT:
00776                 $this->debug( $val, __FUNCTION__, $debug );
00777 
00778                 return $this->isShort( $val );
00779             case (string)Exif::LONG:
00780                 $this->debug( $val, __FUNCTION__, $debug );
00781 
00782                 return $this->isLong( $val );
00783             case (string)Exif::RATIONAL:
00784                 $this->debug( $val, __FUNCTION__, $debug );
00785 
00786                 return $this->isRational( $val );
00787             case (string)Exif::SHORT_OR_LONG:
00788                 $this->debug( $val, __FUNCTION__, $debug );
00789 
00790                 return $this->isShort( $val ) || $this->isLong( $val );
00791             case (string)Exif::UNDEFINED:
00792                 $this->debug( $val, __FUNCTION__, $debug );
00793 
00794                 return $this->isUndefined( $val );
00795             case (string)Exif::SLONG:
00796                 $this->debug( $val, __FUNCTION__, $debug );
00797 
00798                 return $this->isSlong( $val );
00799             case (string)Exif::SRATIONAL:
00800                 $this->debug( $val, __FUNCTION__, $debug );
00801 
00802                 return $this->isSrational( $val );
00803             case (string)Exif::IGNORE:
00804                 $this->debug( $val, __FUNCTION__, $debug );
00805 
00806                 return false;
00807             default:
00808                 $this->debug( $val, __FUNCTION__, "The tag '$tag' is unknown" );
00809 
00810                 return false;
00811         }
00812     }
00813 
00821     private function debug( $in, $fname, $action = null ) {
00822         if ( !$this->log ) {
00823             return;
00824         }
00825         $type = gettype( $in );
00826         $class = ucfirst( __CLASS__ );
00827         if ( is_array( $in ) ) {
00828             $in = print_r( $in, true );
00829         }
00830 
00831         if ( $action === true ) {
00832             wfDebugLog( $this->log, "$class::$fname: accepted: '$in' (type: $type)" );
00833         } elseif ( $action === false ) {
00834             wfDebugLog( $this->log, "$class::$fname: rejected: '$in' (type: $type)" );
00835         } elseif ( $action === null ) {
00836             wfDebugLog( $this->log, "$class::$fname: input was: '$in' (type: $type)" );
00837         } else {
00838             wfDebugLog( $this->log, "$class::$fname: $action (type: $type; content: '$in')" );
00839         }
00840     }
00841 
00848     private function debugFile( $fname, $io ) {
00849         if ( !$this->log ) {
00850             return;
00851         }
00852         $class = ucfirst( __CLASS__ );
00853         if ( $io ) {
00854             wfDebugLog( $this->log, "$class::$fname: begin processing: '{$this->basename}'" );
00855         } else {
00856             wfDebugLog( $this->log, "$class::$fname: end processing: '{$this->basename}'" );
00857         }
00858     }
00859 }