MediaWiki  master
Exif.php
Go to the documentation of this file.
1 <?php
28 use Wikimedia\AtEase\AtEase;
29 
34 class Exif {
36  private const BYTE = 1;
37 
41  private const ASCII = 2;
42 
44  private const SHORT = 3;
45 
47  private const LONG = 4;
48 
52  private const RATIONAL = 5;
53 
55  private const SHORT_OR_LONG = 6;
56 
58  private const UNDEFINED = 7;
59 
61  private const SLONG = 9;
62 
66  private const SRATIONAL = 10;
67 
69  private const IGNORE = -1;
70 
75  private $mExifTags;
76 
78  private $mRawExifData;
79 
85 
87  private $file;
88 
90  private $basename;
91 
93  private $log = false;
94 
98  private $byteOrder;
99 
110  public function __construct( $file, $byteOrder = '' ) {
119  $this->mExifTags = [
120  # TIFF Rev. 6.0 Attribute Information (p22)
121  'IFD0' => [
122  # Tags relating to image structure
123  'ImageWidth' => self::SHORT_OR_LONG, # Image width
124  'ImageLength' => self::SHORT_OR_LONG, # Image height
125  'BitsPerSample' => [ self::SHORT, 3 ], # Number of bits per component
126  # "When a primary image is JPEG compressed, this designation is not"
127  # "necessary and is omitted." (p23)
128  'Compression' => self::SHORT, # Compression scheme #p23
129  'PhotometricInterpretation' => self::SHORT, # Pixel composition #p23
130  'Orientation' => self::SHORT, # Orientation of image #p24
131  'SamplesPerPixel' => self::SHORT, # Number of components
132  'PlanarConfiguration' => self::SHORT, # Image data arrangement #p24
133  'YCbCrSubSampling' => [ self::SHORT, 2 ], # Subsampling ratio of Y to C #p24
134  'YCbCrPositioning' => self::SHORT, # Y and C positioning #p24-25
135  'XResolution' => self::RATIONAL, # Image resolution in width direction
136  'YResolution' => self::RATIONAL, # Image resolution in height direction
137  'ResolutionUnit' => self::SHORT, # Unit of X and Y resolution #(p26)
138 
139  # Tags relating to recording offset
140  'StripOffsets' => self::SHORT_OR_LONG, # Image data location
141  'RowsPerStrip' => self::SHORT_OR_LONG, # Number of rows per strip
142  'StripByteCounts' => self::SHORT_OR_LONG, # Bytes per compressed strip
143  'JPEGInterchangeFormat' => self::SHORT_OR_LONG, # Offset to JPEG SOI
144  'JPEGInterchangeFormatLength' => self::SHORT_OR_LONG, # Bytes of JPEG data
145 
146  # Tags relating to image data characteristics
147  'TransferFunction' => self::IGNORE, # Transfer function
148  'WhitePoint' => [ self::RATIONAL, 2 ], # White point chromaticity
149  'PrimaryChromaticities' => [ self::RATIONAL, 6 ], # Chromaticities of primarities
150  # Color space transformation matrix coefficients #p27
151  'YCbCrCoefficients' => [ self::RATIONAL, 3 ],
152  'ReferenceBlackWhite' => [ self::RATIONAL, 6 ], # Pair of black and white reference values
153 
154  # Other tags
155  'DateTime' => self::ASCII, # File change date and time
156  'ImageDescription' => self::ASCII, # Image title
157  'Make' => self::ASCII, # Image input equipment manufacturer
158  'Model' => self::ASCII, # Image input equipment model
159  'Software' => self::ASCII, # Software used
160  'Artist' => self::ASCII, # Person who created the image
161  'Copyright' => self::ASCII, # Copyright holder
162  ],
163 
164  # Exif IFD Attribute Information (p30-31)
165  'EXIF' => [
166  # @todo NOTE: Nonexistence of this field is taken to mean nonconformance
167  # to the Exif 2.1 AND 2.2 standards
168  'ExifVersion' => self::UNDEFINED, # Exif version
169  'FlashPixVersion' => self::UNDEFINED, # Supported Flashpix version #p32
170 
171  # Tags relating to Image Data Characteristics
172  'ColorSpace' => self::SHORT, # Color space information #p32
173 
174  # Tags relating to image configuration
175  'ComponentsConfiguration' => self::UNDEFINED, # Meaning of each component #p33
176  'CompressedBitsPerPixel' => self::RATIONAL, # Image compression mode
177  'PixelYDimension' => self::SHORT_OR_LONG, # Valid image height
178  'PixelXDimension' => self::SHORT_OR_LONG, # Valid image width
179 
180  # Tags relating to related user information
181  'MakerNote' => self::IGNORE, # Manufacturer notes
182  'UserComment' => self::UNDEFINED, # User comments #p34
183 
184  # Tags relating to related file information
185  'RelatedSoundFile' => self::ASCII, # Related audio file
186 
187  # Tags relating to date and time
188  'DateTimeOriginal' => self::ASCII, # Date and time of original data generation #p36
189  'DateTimeDigitized' => self::ASCII, # Date and time of original data generation
190  'SubSecTime' => self::ASCII, # DateTime subseconds
191  'SubSecTimeOriginal' => self::ASCII, # DateTimeOriginal subseconds
192  'SubSecTimeDigitized' => self::ASCII, # DateTimeDigitized subseconds
193 
194  # Tags relating to picture-taking conditions (p31)
195  'ExposureTime' => self::RATIONAL, # Exposure time
196  'FNumber' => self::RATIONAL, # F Number
197  'ExposureProgram' => self::SHORT, # Exposure Program #p38
198  'SpectralSensitivity' => self::ASCII, # Spectral sensitivity
199  'ISOSpeedRatings' => self::SHORT, # ISO speed rating
200  'OECF' => self::IGNORE,
201  # Optoelectronic conversion factor. Note: We don't have support for this atm.
202  'ShutterSpeedValue' => self::SRATIONAL, # Shutter speed
203  'ApertureValue' => self::RATIONAL, # Aperture
204  'BrightnessValue' => self::SRATIONAL, # Brightness
205  'ExposureBiasValue' => self::SRATIONAL, # Exposure bias
206  'MaxApertureValue' => self::RATIONAL, # Maximum land aperture
207  'SubjectDistance' => self::RATIONAL, # Subject distance
208  'MeteringMode' => self::SHORT, # Metering mode #p40
209  'LightSource' => self::SHORT, # Light source #p40-41
210  'Flash' => self::SHORT, # Flash #p41-42
211  'FocalLength' => self::RATIONAL, # Lens focal length
212  'SubjectArea' => [ self::SHORT, 4 ], # Subject area
213  'FlashEnergy' => self::RATIONAL, # Flash energy
214  'SpatialFrequencyResponse' => self::IGNORE, # Spatial frequency response. Not supported atm.
215  'FocalPlaneXResolution' => self::RATIONAL, # Focal plane X resolution
216  'FocalPlaneYResolution' => self::RATIONAL, # Focal plane Y resolution
217  'FocalPlaneResolutionUnit' => self::SHORT, # Focal plane resolution unit #p46
218  'SubjectLocation' => [ self::SHORT, 2 ], # Subject location
219  'ExposureIndex' => self::RATIONAL, # Exposure index
220  'SensingMethod' => self::SHORT, # Sensing method #p46
221  'FileSource' => self::UNDEFINED, # File source #p47
222  'SceneType' => self::UNDEFINED, # Scene type #p47
223  'CFAPattern' => self::IGNORE, # CFA pattern. not supported atm.
224  'CustomRendered' => self::SHORT, # Custom image processing #p48
225  'ExposureMode' => self::SHORT, # Exposure mode #p48
226  'WhiteBalance' => self::SHORT, # White Balance #p49
227  'DigitalZoomRatio' => self::RATIONAL, # Digital zoom ration
228  'FocalLengthIn35mmFilm' => self::SHORT, # Focal length in 35 mm film
229  'SceneCaptureType' => self::SHORT, # Scene capture type #p49
230  'GainControl' => self::SHORT, # Scene control #p49-50
231  'Contrast' => self::SHORT, # Contrast #p50
232  'Saturation' => self::SHORT, # Saturation #p50
233  'Sharpness' => self::SHORT, # Sharpness #p50
234  'DeviceSettingDescription' => self::IGNORE,
235  # Device settings description. This could maybe be supported. Need to find an
236  # example file that uses this to see if it has stuff of interest in it.
237  'SubjectDistanceRange' => self::SHORT, # Subject distance range #p51
238 
239  'ImageUniqueID' => self::ASCII, # Unique image ID
240  ],
241 
242  # GPS Attribute Information (p52)
243  'GPS' => [
244  'GPSVersion' => self::UNDEFINED,
245  # Should be an array of 4 Exif::BYTE's. However php treats it as an undefined
246  # Note exif standard calls this GPSVersionID, but php doesn't like the id suffix
247  'GPSLatitudeRef' => self::ASCII, # North or South Latitude #p52-53
248  'GPSLatitude' => [ self::RATIONAL, 3 ], # Latitude
249  'GPSLongitudeRef' => self::ASCII, # East or West Longitude #p53
250  'GPSLongitude' => [ self::RATIONAL, 3 ], # Longitude
251  'GPSAltitudeRef' => self::UNDEFINED,
252  # Altitude reference. Note, the exif standard says this should be an EXIF::Byte,
253  # but php seems to disagree.
254  'GPSAltitude' => self::RATIONAL, # Altitude
255  'GPSTimeStamp' => [ self::RATIONAL, 3 ], # GPS time (atomic clock)
256  'GPSSatellites' => self::ASCII, # Satellites used for measurement
257  'GPSStatus' => self::ASCII, # Receiver status #p54
258  'GPSMeasureMode' => self::ASCII, # Measurement mode #p54-55
259  'GPSDOP' => self::RATIONAL, # Measurement precision
260  'GPSSpeedRef' => self::ASCII, # Speed unit #p55
261  'GPSSpeed' => self::RATIONAL, # Speed of GPS receiver
262  'GPSTrackRef' => self::ASCII, # Reference for direction of movement #p55
263  'GPSTrack' => self::RATIONAL, # Direction of movement
264  'GPSImgDirectionRef' => self::ASCII, # Reference for direction of image #p56
265  'GPSImgDirection' => self::RATIONAL, # Direction of image
266  'GPSMapDatum' => self::ASCII, # Geodetic survey data used
267  'GPSDestLatitudeRef' => self::ASCII, # Reference for latitude of destination #p56
268  'GPSDestLatitude' => [ self::RATIONAL, 3 ], # Latitude destination
269  'GPSDestLongitudeRef' => self::ASCII, # Reference for longitude of destination #p57
270  'GPSDestLongitude' => [ self::RATIONAL, 3 ], # Longitude of destination
271  'GPSDestBearingRef' => self::ASCII, # Reference for bearing of destination #p57
272  'GPSDestBearing' => self::RATIONAL, # Bearing of destination
273  'GPSDestDistanceRef' => self::ASCII, # Reference for distance to destination #p57-58
274  'GPSDestDistance' => self::RATIONAL, # Distance to destination
275  'GPSProcessingMethod' => self::UNDEFINED, # Name of GPS processing method
276  'GPSAreaInformation' => self::UNDEFINED, # Name of GPS area
277  'GPSDateStamp' => self::ASCII, # GPS date
278  'GPSDifferential' => self::SHORT, # GPS differential correction
279  ],
280  ];
281 
282  $this->file = $file;
283  $this->basename = wfBaseName( $this->file );
284  if ( $byteOrder === 'BE' || $byteOrder === 'LE' ) {
285  $this->byteOrder = $byteOrder;
286  } else {
287  // Only give a warning for b/c, since originally we didn't
288  // require this. The number of things affected by this is
289  // rather small.
290  wfWarn( 'Exif class did not have byte order specified. ' .
291  'Some properties may be decoded incorrectly.' );
292  $this->byteOrder = 'BE'; // BE seems about twice as popular as LE in jpg's.
293  }
294 
295  $this->debugFile( __FUNCTION__, true );
296  if ( function_exists( 'exif_read_data' ) ) {
297  AtEase::suppressWarnings();
298  $data = exif_read_data( $this->file, '', true );
299  AtEase::restoreWarnings();
300  } else {
301  throw new MWException( "Internal error: exif_read_data not present. " .
302  "\$wgShowEXIF may be incorrectly set or not checked by an extension." );
303  }
309  $this->mRawExifData = $data ?: [];
310  $this->makeFilteredData();
311  $this->collapseData();
312  $this->debugFile( __FUNCTION__, false );
313  }
314 
318  private function makeFilteredData() {
319  $this->mFilteredExifData = [];
320 
321  foreach ( $this->mRawExifData as $section => $data ) {
322  if ( !array_key_exists( $section, $this->mExifTags ) ) {
323  $this->debug( $section, __FUNCTION__, "'$section' is not a valid Exif section" );
324  continue;
325  }
326 
327  foreach ( $data as $tag => $value ) {
328  if ( !array_key_exists( $tag, $this->mExifTags[$section] ) ) {
329  $this->debug( $tag, __FUNCTION__, "'$tag' is not a valid tag in '$section'" );
330  continue;
331  }
332 
333  if ( $this->validate( $section, $tag, $value ) ) {
334  // This is ok, as the tags in the different sections do not conflict.
335  // except in computed and thumbnail section, which we don't use.
336  $this->mFilteredExifData[$tag] = $value;
337  } else {
338  $this->debug( $value, __FUNCTION__, "'$tag' contained invalid data" );
339  }
340  }
341  }
342  }
343 
362  private function collapseData() {
363  $this->exifGPStoNumber( 'GPSLatitude' );
364  $this->exifGPStoNumber( 'GPSDestLatitude' );
365  $this->exifGPStoNumber( 'GPSLongitude' );
366  $this->exifGPStoNumber( 'GPSDestLongitude' );
367 
368  if ( isset( $this->mFilteredExifData['GPSAltitude'] ) ) {
369  // We know altitude data is a <num>/<denom> from the validation
370  // functions ran earlier. But multiplying such a string by -1
371  // doesn't work well, so convert.
372  list( $num, $denom ) = explode( '/', $this->mFilteredExifData['GPSAltitude'], 2 );
373  $this->mFilteredExifData['GPSAltitude'] = (int)$num / (int)$denom;
374 
375  if ( isset( $this->mFilteredExifData['GPSAltitudeRef'] ) ) {
376  switch ( $this->mFilteredExifData['GPSAltitudeRef'] ) {
377  case "\0":
378  // Above sea level
379  break;
380  case "\1":
381  // Below sea level
382  $this->mFilteredExifData['GPSAltitude'] *= -1;
383  break;
384  default:
385  // Invalid
386  unset( $this->mFilteredExifData['GPSAltitude'] );
387  break;
388  }
389  }
390  }
391  unset( $this->mFilteredExifData['GPSAltitudeRef'] );
392 
393  $this->exifPropToOrd( 'FileSource' );
394  $this->exifPropToOrd( 'SceneType' );
395 
396  $this->charCodeString( 'UserComment' );
397  $this->charCodeString( 'GPSProcessingMethod' );
398  $this->charCodeString( 'GPSAreaInformation' );
399 
400  // ComponentsConfiguration should really be an array instead of a string...
401  // This turns a string of binary numbers into an array of numbers.
402 
403  if ( isset( $this->mFilteredExifData['ComponentsConfiguration'] ) ) {
404  $val = $this->mFilteredExifData['ComponentsConfiguration'];
405  $ccVals = [];
406 
407  $strLen = strlen( $val );
408  for ( $i = 0; $i < $strLen; $i++ ) {
409  $ccVals[$i] = ord( substr( $val, $i, 1 ) );
410  }
411  $ccVals['_type'] = 'ol'; // this is for formatting later.
412  $this->mFilteredExifData['ComponentsConfiguration'] = $ccVals;
413  }
414 
415  // GPSVersion(ID) is treated as the wrong type by php exif support.
416  // Go through each byte turning it into a version string.
417  // For example: "\x02\x02\x00\x00" -> "2.2.0.0"
418 
419  // Also change exif tag name from GPSVersion (what php exif thinks it is)
420  // to GPSVersionID (what the exif standard thinks it is).
421 
422  if ( isset( $this->mFilteredExifData['GPSVersion'] ) ) {
423  $val = $this->mFilteredExifData['GPSVersion'];
424  $newVal = '';
425 
426  $strLen = strlen( $val );
427  for ( $i = 0; $i < $strLen; $i++ ) {
428  if ( $i !== 0 ) {
429  $newVal .= '.';
430  }
431  $newVal .= ord( substr( $val, $i, 1 ) );
432  }
433 
434  if ( $this->byteOrder === 'LE' ) {
435  // Need to reverse the string
436  $newVal2 = '';
437  for ( $i = strlen( $newVal ) - 1; $i >= 0; $i-- ) {
438  $newVal2 .= substr( $newVal, $i, 1 );
439  }
440  $this->mFilteredExifData['GPSVersionID'] = $newVal2;
441  } else {
442  $this->mFilteredExifData['GPSVersionID'] = $newVal;
443  }
444  unset( $this->mFilteredExifData['GPSVersion'] );
445  }
446  }
447 
454  private function charCodeString( $prop ) {
455  if ( isset( $this->mFilteredExifData[$prop] ) ) {
456  if ( strlen( $this->mFilteredExifData[$prop] ) <= 8 ) {
457  // invalid. Must be at least 9 bytes long.
458 
459  $this->debug( $this->mFilteredExifData[$prop], __FUNCTION__, false );
460  unset( $this->mFilteredExifData[$prop] );
461 
462  return;
463  }
464  $charCode = substr( $this->mFilteredExifData[$prop], 0, 8 );
465  $val = substr( $this->mFilteredExifData[$prop], 8 );
466 
467  switch ( $charCode ) {
468  case "JIS\x00\x00\x00\x00\x00":
469  $charset = "Shift-JIS";
470  break;
471  case "UNICODE\x00":
472  $charset = "UTF-16" . $this->byteOrder;
473  break;
474  default: // ascii or undefined.
475  $charset = "";
476  break;
477  }
478  if ( $charset ) {
479  AtEase::suppressWarnings();
480  $val = iconv( $charset, 'UTF-8//IGNORE', $val );
481  AtEase::restoreWarnings();
482  } else {
483  // if valid utf-8, assume that, otherwise assume windows-1252
484  $valCopy = $val;
485  UtfNormal\Validator::quickIsNFCVerify( $valCopy ); // validates $valCopy.
486  if ( $valCopy !== $val ) {
487  AtEase::suppressWarnings();
488  $val = iconv( 'Windows-1252', 'UTF-8//IGNORE', $val );
489  AtEase::restoreWarnings();
490  }
491  }
492 
493  // trim and check to make sure not only whitespace.
494  $val = trim( $val );
495  if ( strlen( $val ) === 0 ) {
496  // only whitespace.
497  $this->debug( $this->mFilteredExifData[$prop], __FUNCTION__, "$prop: Is only whitespace" );
498  unset( $this->mFilteredExifData[$prop] );
499 
500  return;
501  }
502 
503  // all's good.
504  $this->mFilteredExifData[$prop] = $val;
505  }
506  }
507 
514  private function exifPropToOrd( $prop ) {
515  if ( isset( $this->mFilteredExifData[$prop] ) ) {
516  $this->mFilteredExifData[$prop] = ord( $this->mFilteredExifData[$prop] );
517  }
518  }
519 
525  private function exifGPStoNumber( $prop ) {
526  $loc =& $this->mFilteredExifData[$prop];
527  $dir =& $this->mFilteredExifData[$prop . 'Ref'];
528  $res = false;
529 
530  if ( isset( $loc ) && isset( $dir )
531  && ( $dir === 'N' || $dir === 'S' || $dir === 'E' || $dir === 'W' )
532  ) {
533  list( $num, $denom ) = explode( '/', $loc[0], 2 );
534  $res = (int)$num / (int)$denom;
535  list( $num, $denom ) = explode( '/', $loc[1], 2 );
536  $res += ( (int)$num / (int)$denom ) * ( 1 / 60 );
537  list( $num, $denom ) = explode( '/', $loc[2], 2 );
538  $res += ( (int)$num / (int)$denom ) * ( 1 / 3600 );
539 
540  if ( $dir === 'S' || $dir === 'W' ) {
541  $res *= -1; // make negative
542  }
543  }
544 
545  // update the exif records.
546 
547  if ( $res !== false ) { // using !== as $res could potentially be 0
548  $this->mFilteredExifData[$prop] = $res;
549  unset( $this->mFilteredExifData[$prop . 'Ref'] );
550  } else { // if invalid
551  unset( $this->mFilteredExifData[$prop] );
552  unset( $this->mFilteredExifData[$prop . 'Ref'] );
553  }
554  }
555 
566  public function getData() {
567  return $this->mRawExifData;
568  }
569 
574  public function getFilteredData() {
576  }
577 
592  public static function version() {
593  return 2; // We don't need no bloddy constants!
594  }
595 
602  private function isByte( $in ) {
603  if ( !is_array( $in ) && sprintf( '%d', $in ) == $in && $in >= 0 && $in <= 255 ) {
604  $this->debug( $in, __FUNCTION__, true );
605 
606  return true;
607  } else {
608  $this->debug( $in, __FUNCTION__, false );
609 
610  return false;
611  }
612  }
613 
618  private function isASCII( $in ) {
619  if ( is_array( $in ) ) {
620  return false;
621  }
622 
623  if ( preg_match( "/[^\x0a\x20-\x7e]/", $in ) ) {
624  $this->debug( $in, __FUNCTION__, 'found a character that is not allowed' );
625 
626  return false;
627  }
628 
629  if ( preg_match( '/^\s*$/', $in ) ) {
630  $this->debug( $in, __FUNCTION__, 'input consisted solely of whitespace' );
631 
632  return false;
633  }
634 
635  return true;
636  }
637 
642  private function isShort( $in ) {
643  if ( !is_array( $in ) && sprintf( '%d', $in ) == $in && $in >= 0 && $in <= 65536 ) {
644  $this->debug( $in, __FUNCTION__, true );
645 
646  return true;
647  } else {
648  $this->debug( $in, __FUNCTION__, false );
649 
650  return false;
651  }
652  }
653 
658  private function isLong( $in ) {
659  if ( !is_array( $in ) && sprintf( '%d', $in ) == $in && $in >= 0 && $in <= 4294967296 ) {
660  $this->debug( $in, __FUNCTION__, true );
661 
662  return true;
663  } else {
664  $this->debug( $in, __FUNCTION__, false );
665 
666  return false;
667  }
668  }
669 
674  private function isRational( $in ) {
675  $m = [];
676 
677  # Avoid division by zero
678  if ( !is_array( $in )
679  && preg_match( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m )
680  ) {
681  return $this->isLong( $m[1] ) && $this->isLong( $m[2] );
682  } else {
683  $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
684 
685  return false;
686  }
687  }
688 
693  private function isUndefined( $in ) {
694  $this->debug( $in, __FUNCTION__, true );
695 
696  return true;
697  }
698 
703  private function isSlong( $in ) {
704  if ( $this->isLong( abs( (float)$in ) ) ) {
705  $this->debug( $in, __FUNCTION__, true );
706 
707  return true;
708  } else {
709  $this->debug( $in, __FUNCTION__, false );
710 
711  return false;
712  }
713  }
714 
719  private function isSrational( $in ) {
720  $m = [];
721 
722  # Avoid division by zero
723  if ( !is_array( $in ) &&
724  preg_match( '/^(-?\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m )
725  ) {
726  return $this->isSlong( $m[0] ) && $this->isSlong( $m[1] );
727  } else {
728  $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
729 
730  return false;
731  }
732  }
733 
745  private function validate( $section, $tag, $val, $recursive = false ) {
746  $debug = "tag is '$tag'";
747  $etype = $this->mExifTags[$section][$tag];
748  $ecount = 1;
749  if ( is_array( $etype ) ) {
750  list( $etype, $ecount ) = $etype;
751  if ( $recursive ) {
752  $ecount = 1; // checking individual elements
753  }
754  }
755 
756  $count = 1;
757  if ( is_array( $val ) ) {
758  $count = count( $val );
759  if ( $ecount != $count ) {
760  $this->debug( $val, __FUNCTION__, "Expected $ecount elements for $tag but got $count" );
761  return false;
762  }
763  }
764  // If there are multiple values, recursively validate each of them.
765  if ( $count > 1 ) {
766  foreach ( $val as $v ) {
767  if ( !$this->validate( $section, $tag, $v, true ) ) {
768  return false;
769  }
770  }
771 
772  return true;
773  }
774  // Does not work if not typecast
775  switch ( (string)$etype ) {
776  case (string)self::BYTE:
777  $this->debug( $val, __FUNCTION__, $debug );
778 
779  return $this->isByte( $val );
780  case (string)self::ASCII:
781  $this->debug( $val, __FUNCTION__, $debug );
782 
783  return $this->isASCII( $val );
784  case (string)self::SHORT:
785  $this->debug( $val, __FUNCTION__, $debug );
786 
787  return $this->isShort( $val );
788  case (string)self::LONG:
789  $this->debug( $val, __FUNCTION__, $debug );
790 
791  return $this->isLong( $val );
792  case (string)self::RATIONAL:
793  $this->debug( $val, __FUNCTION__, $debug );
794 
795  return $this->isRational( $val );
796  case (string)self::SHORT_OR_LONG:
797  $this->debug( $val, __FUNCTION__, $debug );
798 
799  return $this->isShort( $val ) || $this->isLong( $val );
800  case (string)self::UNDEFINED:
801  $this->debug( $val, __FUNCTION__, $debug );
802 
803  return $this->isUndefined( $val );
804  case (string)self::SLONG:
805  $this->debug( $val, __FUNCTION__, $debug );
806 
807  return $this->isSlong( $val );
808  case (string)self::SRATIONAL:
809  $this->debug( $val, __FUNCTION__, $debug );
810 
811  return $this->isSrational( $val );
812  case (string)self::IGNORE:
813  $this->debug( $val, __FUNCTION__, $debug );
814 
815  return false;
816  default:
817  $this->debug( $val, __FUNCTION__, "The tag '$tag' is unknown" );
818 
819  return false;
820  }
821  }
822 
830  private function debug( $in, $fname, $action = null ) {
831  if ( !$this->log ) {
832  return;
833  }
834  $type = gettype( $in );
835  $class = ucfirst( __CLASS__ );
836  if ( is_array( $in ) ) {
837  $in = print_r( $in, true );
838  }
839 
840  if ( $action === true ) {
841  wfDebugLog( $this->log, "$class::$fname: accepted: '$in' (type: $type)" );
842  } elseif ( $action === false ) {
843  wfDebugLog( $this->log, "$class::$fname: rejected: '$in' (type: $type)" );
844  } elseif ( $action === null ) {
845  wfDebugLog( $this->log, "$class::$fname: input was: '$in' (type: $type)" );
846  } else {
847  wfDebugLog( $this->log, "$class::$fname: $action (type: $type; content: '$in')" );
848  }
849  }
850 
857  private function debugFile( $fname, $io ) {
858  if ( !$this->log ) {
859  return;
860  }
861  $class = ucfirst( __CLASS__ );
862  if ( $io ) {
863  wfDebugLog( $this->log, "$class::$fname: begin processing: '{$this->basename}'" );
864  } else {
865  wfDebugLog( $this->log, "$class::$fname: end processing: '{$this->basename}'" );
866  }
867  }
868 }
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
wfBaseName( $path, $suffix='')
Return the final portion of a pathname.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Class to extract and validate Exif data from jpeg (and possibly tiff) files.
Definition: Exif.php:34
const SRATIONAL
Two SLONGs.
Definition: Exif.php:66
validate( $section, $tag, $val, $recursive=false)
#-
Definition: Exif.php:745
isRational( $in)
Definition: Exif.php:674
const BYTE
An 8-bit (1-byte) unsigned integer.
Definition: Exif.php:36
exifPropToOrd( $prop)
Convert an Exif::UNDEFINED from a raw binary string to its value.
Definition: Exif.php:514
string false $log
The private log to log to, e.g.
Definition: Exif.php:93
getData()
#-
Definition: Exif.php:566
const SHORT_OR_LONG
A 16-bit (2-byte) or 32-bit (4-byte) unsigned integer.
Definition: Exif.php:55
string $file
The file being processed.
Definition: Exif.php:87
const RATIONAL
Two LONGs.
Definition: Exif.php:52
array $mFilteredExifData
A Filtered version of $mRawExifData that has been pruned of invalid tags and tags that contain conten...
Definition: Exif.php:84
const UNDEFINED
An 8-bit byte that can take any value depending on the field definition.
Definition: Exif.php:58
isUndefined( $in)
Definition: Exif.php:693
string $byteOrder
The byte order of the file.
Definition: Exif.php:98
static version()
#-
Definition: Exif.php:592
const ASCII
An 8-bit byte containing one 7-bit ASCII code.
Definition: Exif.php:41
isSrational( $in)
Definition: Exif.php:719
getFilteredData()
Get $this->mFilteredExifData.
Definition: Exif.php:574
debug( $in, $fname, $action=null)
Convenience function for debugging output.
Definition: Exif.php:830
const SHORT
A 16-bit (2-byte) unsigned integer.
Definition: Exif.php:44
charCodeString( $prop)
Do userComment tags and similar.
Definition: Exif.php:454
const LONG
A 32-bit (4-byte) unsigned integer.
Definition: Exif.php:47
array $mExifTags
Exif tags grouped by category, the tagname itself is the key and the type is the value,...
Definition: Exif.php:75
array $mRawExifData
The raw Exif data returned by exif_read_data()
Definition: Exif.php:78
__construct( $file, $byteOrder='')
Definition: Exif.php:110
makeFilteredData()
Make $this->mFilteredExifData.
Definition: Exif.php:318
isLong( $in)
Definition: Exif.php:658
string $basename
The basename of the file being processed.
Definition: Exif.php:90
isSlong( $in)
Definition: Exif.php:703
isASCII( $in)
Definition: Exif.php:618
collapseData()
Collapse some fields together.
Definition: Exif.php:362
isShort( $in)
Definition: Exif.php:642
const IGNORE
A fake value for things we don't want or don't support.
Definition: Exif.php:69
debugFile( $fname, $io)
Convenience function for debugging output.
Definition: Exif.php:857
exifGPStoNumber( $prop)
Convert gps in exif form to a single floating point number for example 10 degrees 2040` S -> -10....
Definition: Exif.php:525
isByte( $in)
Validates if a tag value is of the type it should be according to the Exif spec.
Definition: Exif.php:602
const SLONG
A 32-bit (4-byte) signed integer (2's complement notation),.
Definition: Exif.php:61
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition: File.php:68
MediaWiki exception.
Definition: MWException.php:29
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:68
$debug
Definition: mcc.php:31