MediaWiki  REL1_19
Exif.php
Go to the documentation of this file.
00001 <?php
00030 class Exif {
00031 
00032         const BYTE      = 1;    
00033         const ASCII     = 2;    
00034         const SHORT     = 3;    
00035         const LONG      = 4;    
00036         const RATIONAL  = 5;    
00037         const UNDEFINED = 7;    
00038         const SLONG     = 9;    
00039         const SRATIONAL = 10;   
00040         const IGNORE    = -1;   // A fake value for things we don't want or don't support.
00041 
00043         /* @var array
00044          * @private
00045          */
00046 
00052         var $mExifTags;
00053 
00057         var $mRawExifData;
00058 
00064         var $mFilteredExifData;
00065 
00069         var $mFormattedExifData;
00070 
00072 
00074         /* @var string
00075          * @private
00076          */
00077 
00081         var $file;
00082 
00086         var $basename;
00087 
00091         var $log = false;
00092 
00097         private $byteOrder;
00099 
00111         function __construct( $file, $byteOrder = '' ) {
00120                 $this->mExifTags = array(
00121                         # TIFF Rev. 6.0 Attribute Information (p22)
00122                         'IFD0' => array(
00123                                 # Tags relating to image structure
00124                                 'ImageWidth' => Exif::SHORT.','.Exif::LONG,             # Image width
00125                                 'ImageLength' => Exif::SHORT.','.Exif::LONG,            # Image height
00126                                 'BitsPerSample' => array( Exif::SHORT, 3 ),             # Number of bits per component
00127                                 # "When a primary image is JPEG compressed, this designation is not"
00128                                 # "necessary and is omitted." (p23)
00129                                 'Compression' => Exif::SHORT,                           # Compression scheme #p23
00130                                 'PhotometricInterpretation' => Exif::SHORT,             # Pixel composition #p23
00131                                 'Orientation' => Exif::SHORT,                           # Orientation of image #p24
00132                                 'SamplesPerPixel' => Exif::SHORT,                       # Number of components
00133                                 'PlanarConfiguration' => Exif::SHORT,                   # Image data arrangement #p24
00134                                 'YCbCrSubSampling' => array( Exif::SHORT, 2),           # Subsampling ratio of Y to C #p24
00135                                 'YCbCrPositioning' => Exif::SHORT,                      # Y and C positioning #p24-25
00136                                 'XResolution' => Exif::RATIONAL,                        # Image resolution in width direction
00137                                 'YResolution' => Exif::RATIONAL,                        # Image resolution in height direction
00138                                 'ResolutionUnit' => Exif::SHORT,                        # Unit of X and Y resolution #(p26)
00139 
00140                                 # Tags relating to recording offset
00141                                 'StripOffsets' => Exif::SHORT.','.Exif::LONG,                   # Image data location
00142                                 'RowsPerStrip' => Exif::SHORT.','.Exif::LONG,                   # Number of rows per strip
00143                                 'StripByteCounts' => Exif::SHORT.','.Exif::LONG,                # Bytes per compressed strip
00144                                 'JPEGInterchangeFormat' => Exif::SHORT.','.Exif::LONG,          # Offset to JPEG SOI
00145                                 'JPEGInterchangeFormatLength' => Exif::SHORT.','.Exif::LONG,    # Bytes of JPEG data
00146 
00147                                 # Tags relating to image data characteristics
00148                                 'TransferFunction' => Exif::IGNORE,                     # Transfer function
00149                                 'WhitePoint' => array( Exif::RATIONAL, 2),              # White point chromaticity
00150                                 'PrimaryChromaticities' => array( Exif::RATIONAL, 6),   # Chromaticities of primarities
00151                                 'YCbCrCoefficients' => array( Exif::RATIONAL, 3),       # Color space transformation matrix coefficients #p27
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.','.Exif::LONG,                # Valid image width
00178                                 'PixelXDimension' => Exif::SHORT.','.Exif::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. \$wgShowEXIF may be incorrectly set or not checked by an extension." );
00302                 }
00308                 $this->mRawExifData = $data ? $data : array();
00309                 $this->makeFilteredData();
00310                 $this->collapseData();
00311                 $this->debugFile( __FUNCTION__, false );
00312         }
00313 
00317         function makeFilteredData() {
00318                 $this->mFilteredExifData = Array();
00319 
00320                 foreach ( array_keys( $this->mRawExifData ) as $section ) {
00321                         if ( !in_array( $section, array_keys( $this->mExifTags ) ) ) {
00322                                 $this->debug( $section , __FUNCTION__, "'$section' is not a valid Exif section" );
00323                                 continue;
00324                         }
00325 
00326                         foreach ( array_keys( $this->mRawExifData[$section] ) as $tag ) {
00327                                 if ( !in_array( $tag, array_keys( $this->mExifTags[$section] ) ) ) {
00328                                         $this->debug( $tag, __FUNCTION__, "'$tag' is not a valid tag in '$section'" );
00329                                         continue;
00330                                 }
00331 
00332                                 $this->mFilteredExifData[$tag] = $this->mRawExifData[$section][$tag];
00333                                 // This is ok, as the tags in the different sections do not conflict.
00334                                 // except in computed and thumbnail section, which we don't use.
00335 
00336                                 $value = $this->mRawExifData[$section][$tag];
00337                                 if ( !$this->validate( $section, $tag, $value ) ) {
00338                                         $this->debug( $value, __FUNCTION__, "'$tag' contained invalid data" );
00339                                         unset( $this->mFilteredExifData[$tag] );
00340                                 }
00341                         }
00342                 }
00343         }
00344 
00363         function collapseData( ) {
00364 
00365                 $this->exifGPStoNumber( 'GPSLatitude' );
00366                 $this->exifGPStoNumber( 'GPSDestLatitude' );
00367                 $this->exifGPStoNumber( 'GPSLongitude' );
00368                 $this->exifGPStoNumber( 'GPSDestLongitude' );
00369 
00370                 if ( isset( $this->mFilteredExifData['GPSAltitude'] ) && isset( $this->mFilteredExifData['GPSAltitudeRef'] ) ) {
00371                         if ( $this->mFilteredExifData['GPSAltitudeRef'] === "\1" ) {
00372                                 $this->mFilteredExifData['GPSAltitude'] *= - 1;
00373                         }
00374                         unset( $this->mFilteredExifData['GPSAltitudeRef'] );
00375                 }
00376 
00377                 $this->exifPropToOrd( 'FileSource' );
00378                 $this->exifPropToOrd( 'SceneType' );
00379 
00380                 $this->charCodeString( 'UserComment' );
00381                 $this->charCodeString( 'GPSProcessingMethod');
00382                 $this->charCodeString( 'GPSAreaInformation' );
00383                 
00384                 //ComponentsConfiguration should really be an array instead of a string...
00385                 //This turns a string of binary numbers into an array of numbers.
00386 
00387                 if ( isset ( $this->mFilteredExifData['ComponentsConfiguration'] ) ) {
00388                         $val = $this->mFilteredExifData['ComponentsConfiguration'];
00389                         $ccVals = array();
00390                         for ($i = 0; $i < strlen($val); $i++) {
00391                                 $ccVals[$i] = ord( substr($val, $i, 1) );
00392                         }
00393                         $ccVals['_type'] = 'ol'; //this is for formatting later.
00394                         $this->mFilteredExifData['ComponentsConfiguration'] = $ccVals;
00395                 }
00396         
00397                 //GPSVersion(ID) is treated as the wrong type by php exif support.
00398                 //Go through each byte turning it into a version string.
00399                 //For example: "\x02\x02\x00\x00" -> "2.2.0.0"
00400 
00401                 //Also change exif tag name from GPSVersion (what php exif thinks it is)
00402                 //to GPSVersionID (what the exif standard thinks it is).
00403 
00404                 if ( isset ( $this->mFilteredExifData['GPSVersion'] ) ) {
00405                         $val = $this->mFilteredExifData['GPSVersion'];
00406                         $newVal = '';
00407                         for ($i = 0; $i < strlen($val); $i++) {
00408                                 if ( $i !== 0 ) {
00409                                         $newVal .= '.';
00410                                 }
00411                                 $newVal .= ord( substr($val, $i, 1) );
00412                         }
00413                         if ( $this->byteOrder === 'LE' ) {
00414                                 // Need to reverse the string
00415                                 $newVal2 = '';
00416                                 for ( $i = strlen( $newVal ) - 1; $i >= 0; $i-- ) {
00417                                         $newVal2 .= substr( $newVal, $i, 1 );
00418                                 }
00419                                 $this->mFilteredExifData['GPSVersionID'] = $newVal2;
00420                         } else {
00421                                 $this->mFilteredExifData['GPSVersionID'] = $newVal;
00422                         }
00423                         unset( $this->mFilteredExifData['GPSVersion'] );
00424                 }
00425 
00426         }
00433         private function charCodeString ( $prop ) {
00434                 if ( isset( $this->mFilteredExifData[$prop] ) ) {
00435 
00436                         if ( strlen($this->mFilteredExifData[$prop]) <= 8 ) {
00437                                 //invalid. Must be at least 9 bytes long.
00438 
00439                                 $this->debug( $this->mFilteredExifData[$prop] , __FUNCTION__, false );
00440                                 unset($this->mFilteredExifData[$prop]);
00441                                 return;
00442                         }
00443                         $charCode = substr( $this->mFilteredExifData[$prop], 0, 8);
00444                         $val = substr( $this->mFilteredExifData[$prop], 8);
00445                         
00446                         
00447                         switch ($charCode) {
00448                                 case "\x4A\x49\x53\x00\x00\x00\x00\x00":
00449                                         //JIS
00450                                         $charset = "Shift-JIS";
00451                                         break;
00452                                 case "UNICODE\x00":
00453                                         $charset = "UTF-16" . $this->byteOrder;
00454                                         break;
00455                                 default: //ascii or undefined.
00456                                         $charset = "";
00457                                         break;
00458                         }
00459                         // This could possibly check to see if iconv is really installed
00460                         // or if we're using the compatibility wrapper in globalFunctions.php
00461                         if ($charset) {
00462                                 wfSuppressWarnings();
00463                                 $val = iconv($charset, 'UTF-8//IGNORE', $val);
00464                                 wfRestoreWarnings();
00465                         } else {
00466                                 // if valid utf-8, assume that, otherwise assume windows-1252
00467                                 $valCopy = $val;
00468                                 UtfNormal::quickIsNFCVerify( $valCopy ); //validates $valCopy.
00469                                 if ( $valCopy !== $val ) {
00470                                         wfSuppressWarnings();
00471                                         $val = iconv('Windows-1252', 'UTF-8//IGNORE', $val);
00472                                         wfRestoreWarnings();
00473                                 }
00474                         }
00475                         
00476                         //trim and check to make sure not only whitespace.
00477                         $val = trim($val);
00478                         if ( strlen( $val ) === 0 ) {
00479                                 //only whitespace.
00480                                 $this->debug( $this->mFilteredExifData[$prop] , __FUNCTION__, "$prop: Is only whitespace" );
00481                                 unset($this->mFilteredExifData[$prop]);
00482                                 return;
00483                         }
00484 
00485                         //all's good.
00486                         $this->mFilteredExifData[$prop] = $val;
00487                 }
00488         }
00495         private function exifPropToOrd ( $prop ) {
00496                 if ( isset( $this->mFilteredExifData[$prop] ) ) {
00497                         $this->mFilteredExifData[$prop] = ord( $this->mFilteredExifData[$prop] );
00498                 }
00499         }
00505         private function exifGPStoNumber ( $prop ) {
00506                 $loc =& $this->mFilteredExifData[$prop];
00507                 $dir =& $this->mFilteredExifData[$prop . 'Ref'];
00508                 $res = false;
00509 
00510                 if ( isset( $loc ) && isset( $dir ) && ( $dir === 'N' || $dir === 'S' || $dir === 'E' || $dir === 'W' ) ) {
00511                         list( $num, $denom ) = explode( '/', $loc[0] );
00512                         $res = $num / $denom;
00513                         list( $num, $denom ) = explode( '/', $loc[1] );
00514                         $res += ( $num / $denom ) * ( 1 / 60 );
00515                         list( $num, $denom ) = explode( '/', $loc[2] );
00516                         $res += ( $num / $denom ) * ( 1 / 3600 );
00517 
00518                         if ( $dir === 'S' || $dir === 'W' ) {
00519                                 $res *= - 1; // make negative
00520                         }
00521                 }
00522 
00523                 // update the exif records.
00524 
00525                 if ( $res !== false ) { // using !== as $res could potentially be 0
00526                         $this->mFilteredExifData[$prop] = $res;
00527                         unset( $this->mFilteredExifData[$prop . 'Ref'] );
00528                 } else { // if invalid
00529                         unset( $this->mFilteredExifData[$prop] );
00530                         unset( $this->mFilteredExifData[$prop . 'Ref'] );
00531                 }
00532         }
00533 
00540         function makeFormattedData( ) {
00541                 wfDeprecated( __METHOD__, '1.18' );
00542                 $this->mFormattedExifData = FormatMetadata::getFormattedData(
00543                         $this->mFilteredExifData );
00544         }
00553         function getData() {
00554                 return $this->mRawExifData;
00555         }
00556 
00560         function getFilteredData() {
00561                 return $this->mFilteredExifData;
00562         }
00563 
00572         function getFormattedData() {
00573                 wfDeprecated( __METHOD__, '1.18' );
00574                 if (!$this->mFormattedExifData) {
00575                         $this->makeFormattedData();
00576                 }
00577                 return $this->mFormattedExifData;
00578         }
00593         public static function version() {
00594                 return 2; // We don't need no bloddy constants!
00595         }
00596 
00605         private function isByte( $in ) {
00606                 if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 255 ) {
00607                         $this->debug( $in, __FUNCTION__, true );
00608                         return true;
00609                 } else {
00610                         $this->debug( $in, __FUNCTION__, false );
00611                         return false;
00612                 }
00613         }
00614 
00619         private function isASCII( $in ) {
00620                 if ( is_array( $in ) ) {
00621                         return false;
00622                 }
00623 
00624                 if ( preg_match( "/[^\x0a\x20-\x7e]/", $in ) ) {
00625                         $this->debug( $in, __FUNCTION__, 'found a character not in our whitelist' );
00626                         return false;
00627                 }
00628 
00629                 if ( preg_match( '/^\s*$/', $in ) ) {
00630                         $this->debug( $in, __FUNCTION__, 'input consisted solely of whitespace' );
00631                         return false;
00632                 }
00633 
00634                 return true;
00635         }
00636 
00641         private function isShort( $in ) {
00642                 if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 65536 ) {
00643                         $this->debug( $in, __FUNCTION__, true );
00644                         return true;
00645                 } else {
00646                         $this->debug( $in, __FUNCTION__, false );
00647                         return false;
00648                 }
00649         }
00650 
00655         private function isLong( $in ) {
00656                 if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 4294967296 ) {
00657                         $this->debug( $in, __FUNCTION__, true );
00658                         return true;
00659                 } else {
00660                         $this->debug( $in, __FUNCTION__, false );
00661                         return false;
00662                 }
00663         }
00664 
00669         private function isRational( $in ) {
00670                 $m = array();
00671                 if ( !is_array( $in ) && @preg_match( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero
00672                         return $this->isLong( $m[1] ) && $this->isLong( $m[2] );
00673                 } else {
00674                         $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
00675                         return false;
00676                 }
00677         }
00678 
00683         private function isUndefined( $in ) {
00684                 $this->debug( $in, __FUNCTION__, true );
00685                 return true;
00686         }
00687 
00692         private function isSlong( $in ) {
00693                 if ( $this->isLong( abs( $in ) ) ) {
00694                         $this->debug( $in, __FUNCTION__, true );
00695                         return true;
00696                 } else {
00697                         $this->debug( $in, __FUNCTION__, false );
00698                         return false;
00699                 }
00700         }
00701 
00706         private function isSrational( $in ) {
00707                 $m = array();
00708                 if ( !is_array( $in ) && preg_match( '/^(-?\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero
00709                         return $this->isSlong( $m[0] ) && $this->isSlong( $m[1] );
00710                 } else {
00711                         $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
00712                         return false;
00713                 }
00714         }
00727         private function validate( $section, $tag, $val, $recursive = false ) {
00728                 $debug = "tag is '$tag'";
00729                 $etype = $this->mExifTags[$section][$tag];
00730                 $ecount = 1;
00731                 if( is_array( $etype ) ) {
00732                         list( $etype, $ecount ) = $etype;
00733                         if ( $recursive )
00734                                 $ecount = 1; // checking individual elements
00735                 }
00736                 $count = count( $val );
00737                 if( $ecount != $count ) {
00738                         $this->debug( $val, __FUNCTION__, "Expected $ecount elements for $tag but got $count" );
00739                         return false;
00740                 }
00741                 if( $count > 1 ) {
00742                         foreach( $val as $v ) { 
00743                                 if( !$this->validate( $section, $tag, $v, true ) ) {
00744                                         return false; 
00745                                 } 
00746                         }
00747                         return true;
00748                 }
00749                 // Does not work if not typecast
00750                 switch( (string)$etype ) {
00751                         case (string)Exif::BYTE:
00752                                 $this->debug( $val, __FUNCTION__, $debug );
00753                                 return $this->isByte( $val );
00754                         case (string)Exif::ASCII:
00755                                 $this->debug( $val, __FUNCTION__, $debug );
00756                                 return $this->isASCII( $val );
00757                         case (string)Exif::SHORT:
00758                                 $this->debug( $val, __FUNCTION__, $debug );
00759                                 return $this->isShort( $val );
00760                         case (string)Exif::LONG:
00761                                 $this->debug( $val, __FUNCTION__, $debug );
00762                                 return $this->isLong( $val );
00763                         case (string)Exif::RATIONAL:
00764                                 $this->debug( $val, __FUNCTION__, $debug );
00765                                 return $this->isRational( $val );
00766                         case (string)Exif::UNDEFINED:
00767                                 $this->debug( $val, __FUNCTION__, $debug );
00768                                 return $this->isUndefined( $val );
00769                         case (string)Exif::SLONG:
00770                                 $this->debug( $val, __FUNCTION__, $debug );
00771                                 return $this->isSlong( $val );
00772                         case (string)Exif::SRATIONAL:
00773                                 $this->debug( $val, __FUNCTION__, $debug );
00774                                 return $this->isSrational( $val );
00775                         case (string)Exif::SHORT.','.Exif::LONG:
00776                                 $this->debug( $val, __FUNCTION__, $debug );
00777                                 return $this->isShort( $val ) || $this->isLong( $val );
00778                         case (string)Exif::IGNORE:
00779                                 $this->debug( $val, __FUNCTION__, $debug );
00780                                 return false;
00781                         default:
00782                                 $this->debug( $val, __FUNCTION__, "The tag '$tag' is unknown" );
00783                                 return false;
00784                 }
00785         }
00786 
00796         private function debug( $in, $fname, $action = null ) {
00797                 if ( !$this->log ) {
00798                         return;
00799                 }
00800                 $type = gettype( $in );
00801                 $class = ucfirst( __CLASS__ );
00802                 if ( $type === 'array' ) {
00803                         $in = print_r( $in, true );
00804                 }
00805 
00806                 if ( $action === true ) {
00807                         wfDebugLog( $this->log, "$class::$fname: accepted: '$in' (type: $type)\n");
00808                 } elseif ( $action === false ) {
00809                         wfDebugLog( $this->log, "$class::$fname: rejected: '$in' (type: $type)\n");
00810                 } elseif ( $action === null ) {
00811                         wfDebugLog( $this->log, "$class::$fname: input was: '$in' (type: $type)\n");
00812                 } else {
00813                         wfDebugLog( $this->log, "$class::$fname: $action (type: $type; content: '$in')\n");
00814                 }
00815         }
00816 
00825         private function debugFile( $fname, $io ) {
00826                 if ( !$this->log ) {
00827                         return;
00828                 }
00829                 $class = ucfirst( __CLASS__ );
00830                 if ( $io ) {
00831                         wfDebugLog( $this->log, "$class::$fname: begin processing: '{$this->basename}'\n" );
00832                 } else {
00833                         wfDebugLog( $this->log, "$class::$fname: end processing: '{$this->basename}'\n" );
00834                 }
00835         }
00836 }
00837