MediaWiki  master
Exif.php
Go to the documentation of this file.
1 <?php
29 use Wikimedia\AtEase\AtEase;
30 
35 class Exif {
37  private const BYTE = 1;
38 
42  private const ASCII = 2;
43 
45  private const SHORT = 3;
46 
48  private const LONG = 4;
49 
53  private const RATIONAL = 5;
54 
56  private const SHORT_OR_LONG = 6;
57 
59  private const UNDEFINED = 7;
60 
62  private const SLONG = 9;
63 
67  private const SRATIONAL = 10;
68 
70  private const IGNORE = -1;
71 
76  private $mExifTags;
77 
79  private $mRawExifData;
80 
85  private $mFilteredExifData;
86 
88  private $file;
89 
91  private $basename;
92 
94  private $log = false;
95 
99  private $byteOrder;
100 
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  # Image width
124  'ImageWidth' => self::SHORT_OR_LONG,
125  # Image height
126  'ImageLength' => self::SHORT_OR_LONG,
127  # Number of bits per component
128  'BitsPerSample' => [ self::SHORT, 3 ],
129 
130  # "When a primary image is JPEG compressed, this designation is not"
131  # "necessary and is omitted." (p23)
132  # Compression scheme #p23
133  'Compression' => self::SHORT,
134  # Pixel composition #p23
135  'PhotometricInterpretation' => self::SHORT,
136  # Orientation of image #p24
137  'Orientation' => self::SHORT,
138  # Number of components
139  'SamplesPerPixel' => self::SHORT,
140  # Image data arrangement #p24
141  'PlanarConfiguration' => self::SHORT,
142  # Subsampling ratio of Y to C #p24
143  'YCbCrSubSampling' => [ self::SHORT, 2 ],
144  # Y and C positioning #p24-25
145  'YCbCrPositioning' => self::SHORT,
146  # Image resolution in width direction
147  'XResolution' => self::RATIONAL,
148  # Image resolution in height direction
149  'YResolution' => self::RATIONAL,
150  # Unit of X and Y resolution #(p26)
151  'ResolutionUnit' => self::SHORT,
152 
153  # Tags relating to recording offset
154  # Image data location
155  'StripOffsets' => self::SHORT_OR_LONG,
156  # Number of rows per strip
157  'RowsPerStrip' => self::SHORT_OR_LONG,
158  # Bytes per compressed strip
159  'StripByteCounts' => self::SHORT_OR_LONG,
160  # Offset to JPEG SOI
161  'JPEGInterchangeFormat' => self::SHORT_OR_LONG,
162  # Bytes of JPEG data
163  'JPEGInterchangeFormatLength' => self::SHORT_OR_LONG,
164 
165  # Tags relating to image data characteristics
166  # Transfer function
167  'TransferFunction' => self::IGNORE,
168  # White point chromaticity
169  'WhitePoint' => [ self::RATIONAL, 2 ],
170  # Chromaticities of primarities
171  'PrimaryChromaticities' => [ self::RATIONAL, 6 ],
172  # Color space transformation matrix coefficients #p27
173  'YCbCrCoefficients' => [ self::RATIONAL, 3 ],
174  # Pair of black and white reference values
175  'ReferenceBlackWhite' => [ self::RATIONAL, 6 ],
176 
177  # Other tags
178  # File change date and time
179  'DateTime' => self::ASCII,
180  # Image title
181  'ImageDescription' => self::ASCII,
182  # Image input equipment manufacturer
183  'Make' => self::ASCII,
184  # Image input equipment model
185  'Model' => self::ASCII,
186  # Software used
187  'Software' => self::ASCII,
188  # Person who created the image
189  'Artist' => self::ASCII,
190  # Copyright holder
191  'Copyright' => self::ASCII,
192  ],
193 
194  # Exif IFD Attribute Information (p30-31)
195  'EXIF' => [
196  # @todo NOTE: Nonexistence of this field is taken to mean non-conformance
197  # to the Exif 2.1 AND 2.2 standards
198  'ExifVersion' => self::UNDEFINED,
199  # Supported Flashpix version #p32
200  'FlashPixVersion' => self::UNDEFINED,
201 
202  # Tags relating to Image Data Characteristics
203  # Color space information #p32
204  'ColorSpace' => self::SHORT,
205 
206  # Tags relating to image configuration
207  # Meaning of each component #p33
208  'ComponentsConfiguration' => self::UNDEFINED,
209  # Image compression mode
210  'CompressedBitsPerPixel' => self::RATIONAL,
211  # Valid image height
212  'PixelYDimension' => self::SHORT_OR_LONG,
213  # Valid image width
214  'PixelXDimension' => self::SHORT_OR_LONG,
215 
216  # Tags relating to related user information
217  # Manufacturer notes
218  'MakerNote' => self::IGNORE,
219  # User comments #p34
220  'UserComment' => self::UNDEFINED,
221 
222  # Tags relating to related file information
223  # Related audio file
224  'RelatedSoundFile' => self::ASCII,
225 
226  # Tags relating to date and time
227  # Date and time of original data generation #p36
228  'DateTimeOriginal' => self::ASCII,
229  # Date and time of original data generation
230  'DateTimeDigitized' => self::ASCII,
231  # DateTime subseconds
232  'SubSecTime' => self::ASCII,
233  # DateTimeOriginal subseconds
234  'SubSecTimeOriginal' => self::ASCII,
235  # DateTimeDigitized subseconds
236  'SubSecTimeDigitized' => self::ASCII,
237 
238  # Tags relating to picture-taking conditions (p31)
239  # Exposure time
240  'ExposureTime' => self::RATIONAL,
241  # F Number
242  'FNumber' => self::RATIONAL,
243  # Exposure Program #p38
244  'ExposureProgram' => self::SHORT,
245  # Spectral sensitivity
246  'SpectralSensitivity' => self::ASCII,
247  # ISO speed rating
248  'ISOSpeedRatings' => self::SHORT,
249 
250  # Optoelectronic conversion factor. Note: We don't have support for this atm.
251  'OECF' => self::IGNORE,
252 
253  # Shutter speed
254  'ShutterSpeedValue' => self::SRATIONAL,
255  # Aperture
256  'ApertureValue' => self::RATIONAL,
257  # Brightness
258  'BrightnessValue' => self::SRATIONAL,
259  # Exposure bias
260  'ExposureBiasValue' => self::SRATIONAL,
261  # Maximum land aperture
262  'MaxApertureValue' => self::RATIONAL,
263  # Subject distance
264  'SubjectDistance' => self::RATIONAL,
265  # Metering mode #p40
266  'MeteringMode' => self::SHORT,
267  # Light source #p40-41
268  'LightSource' => self::SHORT,
269  # Flash #p41-42
270  'Flash' => self::SHORT,
271  # Lens focal length
272  'FocalLength' => self::RATIONAL,
273  # Subject area
274  'SubjectArea' => [ self::SHORT, 4 ],
275  # Flash energy
276  'FlashEnergy' => self::RATIONAL,
277  # Spatial frequency response. Not supported atm.
278  'SpatialFrequencyResponse' => self::IGNORE,
279  # Focal plane X resolution
280  'FocalPlaneXResolution' => self::RATIONAL,
281  # Focal plane Y resolution
282  'FocalPlaneYResolution' => self::RATIONAL,
283  # Focal plane resolution unit #p46
284  'FocalPlaneResolutionUnit' => self::SHORT,
285  # Subject location
286  'SubjectLocation' => [ self::SHORT, 2 ],
287  # Exposure index
288  'ExposureIndex' => self::RATIONAL,
289  # Sensing method #p46
290  'SensingMethod' => self::SHORT,
291  # File source #p47
292  'FileSource' => self::UNDEFINED,
293  # Scene type #p47
294  'SceneType' => self::UNDEFINED,
295  # CFA pattern. not supported atm.
296  'CFAPattern' => self::IGNORE,
297  # Custom image processing #p48
298  'CustomRendered' => self::SHORT,
299  # Exposure mode #p48
300  'ExposureMode' => self::SHORT,
301  # White Balance #p49
302  'WhiteBalance' => self::SHORT,
303  # Digital zoom ratio
304  'DigitalZoomRatio' => self::RATIONAL,
305  # Focal length in 35 mm film
306  'FocalLengthIn35mmFilm' => self::SHORT,
307  # Scene capture type #p49
308  'SceneCaptureType' => self::SHORT,
309  # Scene control #p49-50
310  'GainControl' => self::SHORT,
311  # Contrast #p50
312  'Contrast' => self::SHORT,
313  # Saturation #p50
314  'Saturation' => self::SHORT,
315  # Sharpness #p50
316  'Sharpness' => self::SHORT,
317 
318  # Device settings description. This could maybe be supported. Need to find an
319  # example file that uses this to see if it has stuff of interest in it.
320  'DeviceSettingDescription' => self::IGNORE,
321 
322  # Subject distance range #p51
323  'SubjectDistanceRange' => self::SHORT,
324 
325  # Unique image ID
326  'ImageUniqueID' => self::ASCII,
327  ],
328 
329  # GPS Attribute Information (p52)
330  'GPS' => [
331  'GPSVersion' => self::UNDEFINED,
332  # Should be an array of 4 Exif::BYTE's. However, php treats it as an undefined
333  # Note exif standard calls this GPSVersionID, but php doesn't like the id suffix
334  # North or South Latitude #p52-53
335  'GPSLatitudeRef' => self::ASCII,
336  # Latitude
337  'GPSLatitude' => [ self::RATIONAL, 3 ],
338  # East or West Longitude #p53
339  'GPSLongitudeRef' => self::ASCII,
340  # Longitude
341  'GPSLongitude' => [ self::RATIONAL, 3 ],
342  'GPSAltitudeRef' => self::UNDEFINED,
343 
344  # Altitude reference. Note, the exif standard says this should be an EXIF::Byte,
345  # but php seems to disagree.
346  # Altitude
347  'GPSAltitude' => self::RATIONAL,
348  # GPS time (atomic clock)
349  'GPSTimeStamp' => [ self::RATIONAL, 3 ],
350  # Satellites used for measurement
351  'GPSSatellites' => self::ASCII,
352  # Receiver status #p54
353  'GPSStatus' => self::ASCII,
354  # Measurement mode #p54-55
355  'GPSMeasureMode' => self::ASCII,
356  # Measurement precision
357  'GPSDOP' => self::RATIONAL,
358  # Speed unit #p55
359  'GPSSpeedRef' => self::ASCII,
360  # Speed of GPS receiver
361  'GPSSpeed' => self::RATIONAL,
362  # Reference for direction of movement #p55
363  'GPSTrackRef' => self::ASCII,
364  # Direction of movement
365  'GPSTrack' => self::RATIONAL,
366  # Reference for direction of image #p56
367  'GPSImgDirectionRef' => self::ASCII,
368  # Direction of image
369  'GPSImgDirection' => self::RATIONAL,
370  # Geodetic survey data used
371  'GPSMapDatum' => self::ASCII,
372  # Reference for latitude of destination #p56
373  'GPSDestLatitudeRef' => self::ASCII,
374  # Latitude destination
375  'GPSDestLatitude' => [ self::RATIONAL, 3 ],
376  # Reference for longitude of destination #p57
377  'GPSDestLongitudeRef' => self::ASCII,
378  # Longitude of destination
379  'GPSDestLongitude' => [ self::RATIONAL, 3 ],
380  # Reference for bearing of destination #p57
381  'GPSDestBearingRef' => self::ASCII,
382  # Bearing of destination
383  'GPSDestBearing' => self::RATIONAL,
384  # Reference for distance to destination #p57-58
385  'GPSDestDistanceRef' => self::ASCII,
386  # Distance to destination
387  'GPSDestDistance' => self::RATIONAL,
388  # Name of GPS processing method
389  'GPSProcessingMethod' => self::UNDEFINED,
390  # Name of GPS area
391  'GPSAreaInformation' => self::UNDEFINED,
392  # GPS date
393  'GPSDateStamp' => self::ASCII,
394  # GPS differential correction
395  'GPSDifferential' => self::SHORT,
396  ],
397  ];
398 
399  $this->file = $file;
400  $this->basename = wfBaseName( $this->file );
401  if ( $byteOrder === 'BE' || $byteOrder === 'LE' ) {
402  $this->byteOrder = $byteOrder;
403  } else {
404  // Only give a warning for b/c, since originally we didn't
405  // require this. The number of things affected by this is
406  // rather small.
407  wfWarn( 'Exif class did not have byte order specified. ' .
408  'Some properties may be decoded incorrectly.' );
409  // BE seems about twice as popular as LE in jpg's.
410  $this->byteOrder = 'BE';
411  }
412 
413  $this->debugFile( __FUNCTION__, true );
414  if ( function_exists( 'exif_read_data' ) ) {
415  AtEase::suppressWarnings();
416  $data = exif_read_data( $this->file, '', true );
417  AtEase::restoreWarnings();
418  } else {
419  throw new ConfigException( "Internal error: exif_read_data not present. " .
420  "\$wgShowEXIF may be incorrectly set or not checked by an extension." );
421  }
427  $this->mRawExifData = $data ?: [];
428  $this->makeFilteredData();
429  $this->collapseData();
430  $this->debugFile( __FUNCTION__, false );
431  }
432 
436  private function makeFilteredData() {
437  $this->mFilteredExifData = [];
438 
439  foreach ( $this->mRawExifData as $section => $data ) {
440  if ( !array_key_exists( $section, $this->mExifTags ) ) {
441  $this->debug( $section, __FUNCTION__, "'$section' is not a valid Exif section" );
442  continue;
443  }
444 
445  foreach ( $data as $tag => $value ) {
446  if ( !array_key_exists( $tag, $this->mExifTags[$section] ) ) {
447  $this->debug( $tag, __FUNCTION__, "'$tag' is not a valid tag in '$section'" );
448  continue;
449  }
450 
451  if ( $this->validate( $section, $tag, $value ) ) {
452  // This is ok, as the tags in the different sections do not conflict.
453  // except in computed and thumbnail section, which we don't use.
454  $this->mFilteredExifData[$tag] = $value;
455  } else {
456  $this->debug( $value, __FUNCTION__, "'$tag' contained invalid data" );
457  }
458  }
459  }
460  }
461 
480  private function collapseData() {
481  $this->exifGPStoNumber( 'GPSLatitude' );
482  $this->exifGPStoNumber( 'GPSDestLatitude' );
483  $this->exifGPStoNumber( 'GPSLongitude' );
484  $this->exifGPStoNumber( 'GPSDestLongitude' );
485 
486  if ( isset( $this->mFilteredExifData['GPSAltitude'] ) ) {
487  // We know altitude data is a <num>/<denom> from the validation
488  // functions ran earlier. But multiplying such a string by -1
489  // doesn't work well, so convert.
490  [ $num, $denom ] = explode( '/', $this->mFilteredExifData['GPSAltitude'], 2 );
491  $this->mFilteredExifData['GPSAltitude'] = (int)$num / (int)$denom;
492 
493  if ( isset( $this->mFilteredExifData['GPSAltitudeRef'] ) ) {
494  switch ( $this->mFilteredExifData['GPSAltitudeRef'] ) {
495  case "\0":
496  // Above sea level
497  break;
498  case "\1":
499  // Below sea level
500  $this->mFilteredExifData['GPSAltitude'] *= -1;
501  break;
502  default:
503  // Invalid
504  unset( $this->mFilteredExifData['GPSAltitude'] );
505  break;
506  }
507  }
508  }
509  unset( $this->mFilteredExifData['GPSAltitudeRef'] );
510 
511  $this->exifPropToOrd( 'FileSource' );
512  $this->exifPropToOrd( 'SceneType' );
513 
514  $this->charCodeString( 'UserComment' );
515  $this->charCodeString( 'GPSProcessingMethod' );
516  $this->charCodeString( 'GPSAreaInformation' );
517 
518  // ComponentsConfiguration should really be an array instead of a string...
519  // This turns a string of binary numbers into an array of numbers.
520 
521  if ( isset( $this->mFilteredExifData['ComponentsConfiguration'] ) ) {
522  $val = $this->mFilteredExifData['ComponentsConfiguration'];
523  $ccVals = [];
524 
525  $strLen = strlen( $val );
526  for ( $i = 0; $i < $strLen; $i++ ) {
527  $ccVals[$i] = ord( substr( $val, $i, 1 ) );
528  }
529  // this is for formatting later.
530  $ccVals['_type'] = 'ol';
531  $this->mFilteredExifData['ComponentsConfiguration'] = $ccVals;
532  }
533 
534  // GPSVersion(ID) is treated as the wrong type by php exif support.
535  // Go through each byte turning it into a version string.
536  // For example: "\x02\x02\x00\x00" -> "2.2.0.0"
537 
538  // Also change exif tag name from GPSVersion (what php exif thinks it is)
539  // to GPSVersionID (what the exif standard thinks it is).
540 
541  if ( isset( $this->mFilteredExifData['GPSVersion'] ) ) {
542  $val = $this->mFilteredExifData['GPSVersion'];
543  $newVal = '';
544 
545  $strLen = strlen( $val );
546  for ( $i = 0; $i < $strLen; $i++ ) {
547  if ( $i !== 0 ) {
548  $newVal .= '.';
549  }
550  $newVal .= ord( substr( $val, $i, 1 ) );
551  }
552 
553  if ( $this->byteOrder === 'LE' ) {
554  // Need to reverse the string
555  $newVal2 = '';
556  for ( $i = strlen( $newVal ) - 1; $i >= 0; $i-- ) {
557  $newVal2 .= substr( $newVal, $i, 1 );
558  }
559  $this->mFilteredExifData['GPSVersionID'] = $newVal2;
560  } else {
561  $this->mFilteredExifData['GPSVersionID'] = $newVal;
562  }
563  unset( $this->mFilteredExifData['GPSVersion'] );
564  }
565  }
566 
573  private function charCodeString( $prop ) {
574  if ( isset( $this->mFilteredExifData[$prop] ) ) {
575  if ( strlen( $this->mFilteredExifData[$prop] ) <= 8 ) {
576  // invalid. Must be at least 9 bytes long.
577 
578  $this->debug( $this->mFilteredExifData[$prop], __FUNCTION__, false );
579  unset( $this->mFilteredExifData[$prop] );
580 
581  return;
582  }
583  $charCode = substr( $this->mFilteredExifData[$prop], 0, 8 );
584  $val = substr( $this->mFilteredExifData[$prop], 8 );
585 
586  switch ( $charCode ) {
587  case "JIS\x00\x00\x00\x00\x00":
588  $charset = "Shift-JIS";
589  break;
590  case "UNICODE\x00":
591  $charset = "UTF-16" . $this->byteOrder;
592  break;
593  default:
594  // ascii or undefined.
595  $charset = "";
596  break;
597  }
598  if ( $charset ) {
599  AtEase::suppressWarnings();
600  $val = iconv( $charset, 'UTF-8//IGNORE', $val );
601  AtEase::restoreWarnings();
602  } else {
603  // if valid utf-8, assume that, otherwise assume windows-1252
604  $valCopy = $val;
605  UtfNormal\Validator::quickIsNFCVerify( $valCopy );
606  if ( $valCopy !== $val ) {
607  AtEase::suppressWarnings();
608  $val = iconv( 'Windows-1252', 'UTF-8//IGNORE', $val );
609  AtEase::restoreWarnings();
610  }
611  }
612 
613  // trim and check to make sure not only whitespace.
614  $val = trim( $val );
615  if ( strlen( $val ) === 0 ) {
616  // only whitespace.
617  $this->debug( $this->mFilteredExifData[$prop], __FUNCTION__, "$prop: Is only whitespace" );
618  unset( $this->mFilteredExifData[$prop] );
619 
620  return;
621  }
622 
623  // all's good.
624  $this->mFilteredExifData[$prop] = $val;
625  }
626  }
627 
634  private function exifPropToOrd( $prop ) {
635  if ( isset( $this->mFilteredExifData[$prop] ) ) {
636  $this->mFilteredExifData[$prop] = ord( $this->mFilteredExifData[$prop] );
637  }
638  }
639 
645  private function exifGPStoNumber( $prop ) {
646  $loc =& $this->mFilteredExifData[$prop];
647  $dir =& $this->mFilteredExifData[$prop . 'Ref'];
648  $res = false;
649 
650  if ( isset( $loc ) && isset( $dir )
651  && ( $dir === 'N' || $dir === 'S' || $dir === 'E' || $dir === 'W' )
652  ) {
653  [ $num, $denom ] = explode( '/', $loc[0], 2 );
654  $res = (int)$num / (int)$denom;
655  [ $num, $denom ] = explode( '/', $loc[1], 2 );
656  $res += ( (int)$num / (int)$denom ) * ( 1 / 60 );
657  [ $num, $denom ] = explode( '/', $loc[2], 2 );
658  $res += ( (int)$num / (int)$denom ) * ( 1 / 3600 );
659 
660  if ( $dir === 'S' || $dir === 'W' ) {
661  // make negative
662  $res *= -1;
663  }
664  }
665 
666  // update the exif records.
667 
668  // using !== as $res could potentially be 0
669  if ( $res !== false ) {
670  $this->mFilteredExifData[$prop] = $res;
671  } else {
672  // if invalid
673  unset( $this->mFilteredExifData[$prop] );
674  }
675  unset( $this->mFilteredExifData[$prop . 'Ref'] );
676  }
677 
688  public function getData() {
689  return $this->mRawExifData;
690  }
691 
696  public function getFilteredData() {
697  return $this->mFilteredExifData;
698  }
699 
714  public static function version() {
715  return 2;
716  }
717 
724  private function isByte( $in ) {
725  if ( !is_array( $in ) && sprintf( '%d', $in ) == $in && $in >= 0 && $in <= 255 ) {
726  $this->debug( $in, __FUNCTION__, true );
727 
728  return true;
729  }
730 
731  $this->debug( $in, __FUNCTION__, false );
732 
733  return false;
734  }
735 
740  private function isASCII( $in ) {
741  if ( is_array( $in ) ) {
742  return false;
743  }
744 
745  if ( preg_match( "/[^\x0a\x20-\x7e]/", $in ) ) {
746  $this->debug( $in, __FUNCTION__, 'found a character that is not allowed' );
747 
748  return false;
749  }
750 
751  if ( preg_match( '/^\s*$/', $in ) ) {
752  $this->debug( $in, __FUNCTION__, 'input consisted solely of whitespace' );
753 
754  return false;
755  }
756 
757  return true;
758  }
759 
764  private function isShort( $in ) {
765  if ( !is_array( $in ) && sprintf( '%d', $in ) == $in && $in >= 0 && $in <= 65536 ) {
766  $this->debug( $in, __FUNCTION__, true );
767 
768  return true;
769  }
770 
771  $this->debug( $in, __FUNCTION__, false );
772 
773  return false;
774  }
775 
780  private function isLong( $in ) {
781  if ( !is_array( $in ) && sprintf( '%d', $in ) == $in && $in >= 0 && $in <= 4294967296 ) {
782  $this->debug( $in, __FUNCTION__, true );
783 
784  return true;
785  }
786 
787  $this->debug( $in, __FUNCTION__, false );
788 
789  return false;
790  }
791 
796  private function isRational( $in ) {
797  $m = [];
798 
799  # Avoid division by zero
800  if ( !is_array( $in )
801  && preg_match( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m )
802  ) {
803  return $this->isLong( $m[1] ) && $this->isLong( $m[2] );
804  }
805 
806  $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
807 
808  return false;
809  }
810 
815  private function isUndefined( $in ) {
816  $this->debug( $in, __FUNCTION__, true );
817 
818  return true;
819  }
820 
825  private function isSlong( $in ) {
826  if ( $this->isLong( abs( (float)$in ) ) ) {
827  $this->debug( $in, __FUNCTION__, true );
828 
829  return true;
830  }
831 
832  $this->debug( $in, __FUNCTION__, false );
833 
834  return false;
835  }
836 
841  private function isSrational( $in ) {
842  $m = [];
843 
844  # Avoid division by zero
845  if ( !is_array( $in ) &&
846  preg_match( '/^(-?\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m )
847  ) {
848  return $this->isSlong( $m[0] ) && $this->isSlong( $m[1] );
849  }
850 
851  $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
852 
853  return false;
854  }
855 
867  private function validate( $section, $tag, $val, $recursive = false ): bool {
868  $debug = "tag is '$tag'";
869  $etype = $this->mExifTags[$section][$tag];
870  $ecount = 1;
871  if ( is_array( $etype ) ) {
872  [ $etype, $ecount ] = $etype;
873  if ( $recursive ) {
874  // checking individual elements
875  $ecount = 1;
876  }
877  }
878 
879  $count = 1;
880  if ( is_array( $val ) ) {
881  $count = count( $val );
882  if ( $ecount !== $count ) {
883  $this->debug( $val, __FUNCTION__, "Expected $ecount elements for $tag but got $count" );
884  return false;
885  }
886  }
887  // If there are multiple values, recursively validate each of them.
888  if ( $count > 1 ) {
889  foreach ( $val as $v ) {
890  if ( !$this->validate( $section, $tag, $v, true ) ) {
891  return false;
892  }
893  }
894 
895  return true;
896  }
897 
898  // NULL values are considered valid. T315202.
899  if ( $val === null ) {
900  return true;
901  }
902 
903  // Does not work if not typecast
904  switch ( (string)$etype ) {
905  case (string)self::BYTE:
906  $this->debug( $val, __FUNCTION__, $debug );
907 
908  return $this->isByte( $val );
909  case (string)self::ASCII:
910  $this->debug( $val, __FUNCTION__, $debug );
911 
912  return $this->isASCII( $val );
913  case (string)self::SHORT:
914  $this->debug( $val, __FUNCTION__, $debug );
915 
916  return $this->isShort( $val );
917  case (string)self::LONG:
918  $this->debug( $val, __FUNCTION__, $debug );
919 
920  return $this->isLong( $val );
921  case (string)self::RATIONAL:
922  $this->debug( $val, __FUNCTION__, $debug );
923 
924  return $this->isRational( $val );
925  case (string)self::SHORT_OR_LONG:
926  $this->debug( $val, __FUNCTION__, $debug );
927 
928  return $this->isShort( $val ) || $this->isLong( $val );
929  case (string)self::UNDEFINED:
930  $this->debug( $val, __FUNCTION__, $debug );
931 
932  return $this->isUndefined( $val );
933  case (string)self::SLONG:
934  $this->debug( $val, __FUNCTION__, $debug );
935 
936  return $this->isSlong( $val );
937  case (string)self::SRATIONAL:
938  $this->debug( $val, __FUNCTION__, $debug );
939 
940  return $this->isSrational( $val );
941  case (string)self::IGNORE:
942  $this->debug( $val, __FUNCTION__, $debug );
943 
944  return false;
945  default:
946  $this->debug( $val, __FUNCTION__, "The tag '$tag' is unknown" );
947 
948  return false;
949  }
950  }
951 
959  private function debug( $in, $fname, $action = null ) {
960  if ( !$this->log ) {
961  return;
962  }
963  $type = gettype( $in );
964  $class = ucfirst( __CLASS__ );
965  if ( is_array( $in ) ) {
966  $in = print_r( $in, true );
967  }
968 
969  if ( $action === true ) {
970  wfDebugLog( $this->log, "$class::$fname: accepted: '$in' (type: $type)" );
971  } elseif ( $action === false ) {
972  wfDebugLog( $this->log, "$class::$fname: rejected: '$in' (type: $type)" );
973  } elseif ( $action === null ) {
974  wfDebugLog( $this->log, "$class::$fname: input was: '$in' (type: $type)" );
975  } else {
976  wfDebugLog( $this->log, "$class::$fname: $action (type: $type; content: '$in')" );
977  }
978  }
979 
986  private function debugFile( $fname, $io ) {
987  if ( !$this->log ) {
988  return;
989  }
990  $class = ucfirst( __CLASS__ );
991  if ( $io ) {
992  wfDebugLog( $this->log, "$class::$fname: begin processing: '{$this->basename}'" );
993  } else {
994  wfDebugLog( $this->log, "$class::$fname: end processing: '{$this->basename}'" );
995  }
996  }
997 }
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:35
getData()
#-
Definition: Exif.php:688
static version()
#-
Definition: Exif.php:714
getFilteredData()
Get $this->mFilteredExifData.
Definition: Exif.php:696
__construct( $file, $byteOrder='')
Definition: Exif.php:110
Exceptions for config failures.
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42