MediaWiki REL1_40
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 } 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:34
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
MediaWiki exception.
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42