MediaWiki REL1_39
Exif.php
Go to the documentation of this file.
1<?php
28use Wikimedia\AtEase\AtEase;
29
34class 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
84 private $mFilteredExifData;
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 # 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 MWException( "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 unset( $this->mFilteredExifData[$prop . 'Ref'] );
672 } else {
673 // if invalid
674 unset( $this->mFilteredExifData[$prop] );
675 unset( $this->mFilteredExifData[$prop . 'Ref'] );
676 }
677 }
678
689 public function getData() {
690 return $this->mRawExifData;
691 }
692
697 public function getFilteredData() {
698 return $this->mFilteredExifData;
699 }
700
715 public static function version() {
716 return 2;
717 }
718
725 private function isByte( $in ) {
726 if ( !is_array( $in ) && sprintf( '%d', $in ) == $in && $in >= 0 && $in <= 255 ) {
727 $this->debug( $in, __FUNCTION__, true );
728
729 return true;
730 }
731
732 $this->debug( $in, __FUNCTION__, false );
733
734 return false;
735 }
736
741 private function isASCII( $in ) {
742 if ( is_array( $in ) ) {
743 return false;
744 }
745
746 if ( preg_match( "/[^\x0a\x20-\x7e]/", $in ) ) {
747 $this->debug( $in, __FUNCTION__, 'found a character that is not allowed' );
748
749 return false;
750 }
751
752 if ( preg_match( '/^\s*$/', $in ) ) {
753 $this->debug( $in, __FUNCTION__, 'input consisted solely of whitespace' );
754
755 return false;
756 }
757
758 return true;
759 }
760
765 private function isShort( $in ) {
766 if ( !is_array( $in ) && sprintf( '%d', $in ) == $in && $in >= 0 && $in <= 65536 ) {
767 $this->debug( $in, __FUNCTION__, true );
768
769 return true;
770 }
771
772 $this->debug( $in, __FUNCTION__, false );
773
774 return false;
775 }
776
781 private function isLong( $in ) {
782 if ( !is_array( $in ) && sprintf( '%d', $in ) == $in && $in >= 0 && $in <= 4294967296 ) {
783 $this->debug( $in, __FUNCTION__, true );
784
785 return true;
786 }
787
788 $this->debug( $in, __FUNCTION__, false );
789
790 return false;
791 }
792
797 private function isRational( $in ) {
798 $m = [];
799
800 # Avoid division by zero
801 if ( !is_array( $in )
802 && preg_match( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m )
803 ) {
804 return $this->isLong( $m[1] ) && $this->isLong( $m[2] );
805 }
806
807 $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
808
809 return false;
810 }
811
816 private function isUndefined( $in ) {
817 $this->debug( $in, __FUNCTION__, true );
818
819 return true;
820 }
821
826 private function isSlong( $in ) {
827 if ( $this->isLong( abs( (float)$in ) ) ) {
828 $this->debug( $in, __FUNCTION__, true );
829
830 return true;
831 }
832
833 $this->debug( $in, __FUNCTION__, false );
834
835 return false;
836 }
837
842 private function isSrational( $in ) {
843 $m = [];
844
845 # Avoid division by zero
846 if ( !is_array( $in ) &&
847 preg_match( '/^(-?\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m )
848 ) {
849 return $this->isSlong( $m[0] ) && $this->isSlong( $m[1] );
850 }
851
852 $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
853
854 return false;
855 }
856
868 private function validate( $section, $tag, $val, $recursive = false ) {
869 $debug = "tag is '$tag'";
870 $etype = $this->mExifTags[$section][$tag];
871 $ecount = 1;
872 if ( is_array( $etype ) ) {
873 [ $etype, $ecount ] = $etype;
874 if ( $recursive ) {
875 // checking individual elements
876 $ecount = 1;
877 }
878 }
879
880 $count = 1;
881 if ( is_array( $val ) ) {
882 $count = count( $val );
883 if ( $ecount != $count ) {
884 $this->debug( $val, __FUNCTION__, "Expected $ecount elements for $tag but got $count" );
885 return false;
886 }
887 }
888 // If there are multiple values, recursively validate each of them.
889 if ( $count > 1 ) {
890 foreach ( $val as $v ) {
891 if ( !$this->validate( $section, $tag, $v, true ) ) {
892 return false;
893 }
894 }
895
896 return true;
897 }
898 // Does not work if not typecast
899 switch ( (string)$etype ) {
900 case (string)self::BYTE:
901 $this->debug( $val, __FUNCTION__, $debug );
902
903 return $this->isByte( $val );
904 case (string)self::ASCII:
905 $this->debug( $val, __FUNCTION__, $debug );
906
907 return $this->isASCII( $val );
908 case (string)self::SHORT:
909 $this->debug( $val, __FUNCTION__, $debug );
910
911 return $this->isShort( $val );
912 case (string)self::LONG:
913 $this->debug( $val, __FUNCTION__, $debug );
914
915 return $this->isLong( $val );
916 case (string)self::RATIONAL:
917 $this->debug( $val, __FUNCTION__, $debug );
918
919 return $this->isRational( $val );
920 case (string)self::SHORT_OR_LONG:
921 $this->debug( $val, __FUNCTION__, $debug );
922
923 return $this->isShort( $val ) || $this->isLong( $val );
924 case (string)self::UNDEFINED:
925 $this->debug( $val, __FUNCTION__, $debug );
926
927 return $this->isUndefined( $val );
928 case (string)self::SLONG:
929 $this->debug( $val, __FUNCTION__, $debug );
930
931 return $this->isSlong( $val );
932 case (string)self::SRATIONAL:
933 $this->debug( $val, __FUNCTION__, $debug );
934
935 return $this->isSrational( $val );
936 case (string)self::IGNORE:
937 $this->debug( $val, __FUNCTION__, $debug );
938
939 return false;
940 default:
941 $this->debug( $val, __FUNCTION__, "The tag '$tag' is unknown" );
942
943 return false;
944 }
945 }
946
954 private function debug( $in, $fname, $action = null ) {
955 if ( !$this->log ) {
956 return;
957 }
958 $type = gettype( $in );
959 $class = ucfirst( __CLASS__ );
960 if ( is_array( $in ) ) {
961 $in = print_r( $in, true );
962 }
963
964 if ( $action === true ) {
965 wfDebugLog( $this->log, "$class::$fname: accepted: '$in' (type: $type)" );
966 } elseif ( $action === false ) {
967 wfDebugLog( $this->log, "$class::$fname: rejected: '$in' (type: $type)" );
968 } elseif ( $action === null ) {
969 wfDebugLog( $this->log, "$class::$fname: input was: '$in' (type: $type)" );
970 } else {
971 wfDebugLog( $this->log, "$class::$fname: $action (type: $type; content: '$in')" );
972 }
973 }
974
981 private function debugFile( $fname, $io ) {
982 if ( !$this->log ) {
983 return;
984 }
985 $class = ucfirst( __CLASS__ );
986 if ( $io ) {
987 wfDebugLog( $this->log, "$class::$fname: begin processing: '{$this->basename}'" );
988 } else {
989 wfDebugLog( $this->log, "$class::$fname: end processing: '{$this->basename}'" );
990 }
991 }
992}
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
getData()
#-
Definition Exif.php:689
static version()
#-
Definition Exif.php:715
getFilteredData()
Get $this->mFilteredExifData.
Definition Exif.php:697
__construct( $file, $byteOrder='')
Definition Exif.php:110
MediaWiki exception.
$debug
Definition mcc.php:31
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42