28use Wikimedia\Timestamp\TimestampException;
66 $this->singleLang = $val;
88 return $obj->makeFormattedData( $tags );
103 $resolutionunit = !isset( $tags[
'ResolutionUnit'] ) || $tags[
'ResolutionUnit'] == 2 ? 2 : 3;
104 unset( $tags[
'ResolutionUnit'] );
106 foreach ( $tags as $tag => &$vals ) {
109 if ( !is_array( $tags[$tag] ) ) {
114 if ( isset( $tags[$tag][
'_type'] ) ) {
115 $type = $tags[$tag][
'_type'];
116 unset( $vals[
'_type'] );
122 if ( $tag ==
'GPSTimeStamp' && count( $vals ) === 3 ) {
125 $h = explode(
'/', $vals[0] );
126 $m = explode(
'/', $vals[1] );
127 $s = explode(
'/', $vals[2] );
142 $tags[$tag] = str_pad( intval( $h[0] / $h[1] ), 2,
'0', STR_PAD_LEFT )
143 .
':' . str_pad( intval( $m[0] / $m[1] ), 2,
'0', STR_PAD_LEFT )
144 .
':' . str_pad( intval(
$s[0] /
$s[1] ), 2,
'0', STR_PAD_LEFT );
147 $time =
wfTimestamp( TS_MW,
'1971:01:01 ' . $tags[$tag] );
149 if ( $time && intval( $time ) > 0 ) {
152 }
catch ( TimestampException $e ) {
163 if ( $tag ===
'Contact' ) {
168 foreach ( $vals as &$val ) {
183 $val = $this->
exifMsg( $tag, $val );
191 case 'PhotometricInterpretation':
205 $val = $this->
exifMsg( $tag, $val );
223 $val = $this->
exifMsg( $tag, $val );
231 case 'PlanarConfiguration':
235 $val = $this->
exifMsg( $tag, $val );
244 case 'YCbCrPositioning':
248 $val = $this->
exifMsg( $tag, $val );
258 switch ( $resolutionunit ) {
273 case 'FlashpixVersion':
274 $val = (int)$val / 100;
281 $val = $this->
exifMsg( $tag, $val );
289 case 'ComponentsConfiguration':
298 $val = $this->
exifMsg( $tag, $val );
307 case 'DateTimeOriginal':
308 case 'DateTimeDigitized':
309 case 'DateTimeReleased':
310 case 'DateTimeExpires':
313 case 'DateTimeMetadata':
314 if ( $val ==
'0000:00:00 00:00:00' || $val ==
' : : : : ' ) {
315 $val = $this->
msg(
'exif-unknowndate' )->text();
316 } elseif ( preg_match(
317 '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/D',
322 if ( $time && intval( $time ) > 0 ) {
325 } elseif ( preg_match(
'/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d)$/D', $val ) ) {
330 if ( $time && intval( $time ) > 0 ) {
333 } elseif ( preg_match(
'/^(?:\d{4}):(?:\d\d):(?:\d\d)$/D', $val ) ) {
336 . substr( $val, 5, 2 )
337 . substr( $val, 8, 2 )
339 if ( $time && intval( $time ) > 0 ) {
346 case 'ExposureProgram':
357 $val = $this->
exifMsg( $tag, $val );
365 case 'SubjectDistance':
380 $val = $this->
exifMsg( $tag, $val );
411 $val = $this->
exifMsg( $tag, $val );
421 'fired' => $val & 0b00000001,
422 'return' => ( $val & 0b00000110 ) >> 1,
423 'mode' => ( $val & 0b00011000 ) >> 3,
424 'function' => ( $val & 0b00100000 ) >> 5,
425 'redeye' => ( $val & 0b01000000 ) >> 6,
429 # We do not need to handle unknown values since all are used.
430 foreach ( $flashDecode as $subTag => $subValue ) {
431 # We do not need any message for zeroed values.
432 if ( $subTag !=
'fired' && $subValue == 0 ) {
435 $fullTag = $tag .
'-' . $subTag;
436 $flashMsgs[] = $this->
exifMsg( $fullTag, $subValue );
438 $val = $this->
getLanguage()->commaList( $flashMsgs );
441 case 'FocalPlaneResolutionUnit':
444 $val = $this->
exifMsg( $tag, $val );
452 case 'SensingMethod':
461 $val = $this->
exifMsg( $tag, $val );
472 $val = $this->
exifMsg( $tag, $val );
483 $val = $this->
exifMsg( $tag, $val );
491 case 'CustomRendered':
503 $val = $this->
exifMsg( $tag, $val );
516 $val = $this->
exifMsg( $tag, $val );
528 $val = $this->
exifMsg( $tag, $val );
536 case 'SceneCaptureType':
542 $val = $this->
exifMsg( $tag, $val );
557 $val = $this->
exifMsg( $tag, $val );
570 $val = $this->
exifMsg( $tag, $val );
583 $val = $this->
exifMsg( $tag, $val );
596 $val = $this->
exifMsg( $tag, $val );
604 case 'SubjectDistanceRange':
610 $val = $this->
exifMsg( $tag, $val );
619 case 'GPSLatitudeRef':
620 case 'GPSDestLatitudeRef':
624 $val = $this->
exifMsg(
'GPSLatitude', $val );
632 case 'GPSLongitudeRef':
633 case 'GPSDestLongitudeRef':
637 $val = $this->
exifMsg(
'GPSLongitude', $val );
647 $val = $this->
exifMsg(
'GPSAltitude',
'below-sealevel', $this->
formatNum( -$val, 3 ) );
649 $val = $this->
exifMsg(
'GPSAltitude',
'above-sealevel', $this->
formatNum( $val, 3 ) );
657 $val = $this->
exifMsg( $tag, $val );
665 case 'GPSMeasureMode':
669 $val = $this->
exifMsg( $tag, $val );
678 case 'GPSImgDirectionRef':
679 case 'GPSDestBearingRef':
683 $val = $this->
exifMsg(
'GPSDirection', $val );
692 case 'GPSDestLatitude':
696 case 'GPSDestLongitude':
705 $val = $this->
exifMsg(
'GPSSpeed', $val );
713 case 'GPSDestDistanceRef':
718 $val = $this->
exifMsg(
'GPSDestDistance', $val );
730 } elseif ( $val <= 5 ) {
732 } elseif ( $val <= 10 ) {
734 } elseif ( $val <= 20 ) {
746 $val = $this->
exifMsg( $tag,
'', $val );
750 if ( is_array( $val ) ) {
751 if ( count( $val ) > 1 ) {
753 $val = $this->
msg(
'exif-software-version-value', $val[0], $val[1] )->text();
756 $val = $this->
exifMsg( $tag,
'', $val[0] );
759 $val = $this->
exifMsg( $tag,
'', $val );
765 $val = $this->
msg(
'exif-exposuretime-format',
768 case 'ISOSpeedRatings':
772 if ( $val ==
'65535' ) {
773 $val = $this->
exifMsg( $tag,
'overflow' );
779 $val = $this->
msg(
'exif-fnumber-format',
784 case 'FocalLengthIn35mmFilm':
785 $val = $this->
msg(
'exif-focallength-format',
789 case 'MaxApertureValue':
790 if ( strpos( $val,
'/' ) !==
false ) {
792 list( $n, $d ) = explode(
'/', $val );
793 if ( is_numeric( $n ) && is_numeric( $d ) ) {
797 if ( is_numeric( $val ) ) {
798 $fNumber = 2 ** ( $val / 2 );
799 if ( $fNumber !==
false ) {
800 $val = $this->
msg(
'exif-maxaperturevalue-value',
809 switch ( strtolower( $val ) ) {
835 case 'SubjectNewsCode':
847 if ( $val == 0 || $val == 9 ) {
849 } elseif ( $val < 5 && $val > 1 ) {
851 } elseif ( $val == 5 ) {
853 } elseif ( $val <= 8 && $val > 5 ) {
857 if ( $urgency !==
'' ) {
858 $val = $this->
exifMsg(
'urgency',
865 case 'OriginalImageHeight':
866 case 'OriginalImageWidth':
867 case 'PixelXDimension':
868 case 'PixelYDimension':
871 $val = $this->
formatNum( $val ) .
' ' . $this->
msg(
'unit-pixel' )->text();
881 case 'ImageDescription':
885 case 'RelatedSoundFile':
886 case 'ImageUniqueID':
887 case 'SpectralSensitivity':
888 case 'GPSSatellites':
892 case 'WorldRegionDest':
894 case 'CountryCodeDest':
895 case 'ProvinceOrStateDest':
897 case 'SublocationDest':
898 case 'WorldRegionCreated':
899 case 'CountryCreated':
900 case 'CountryCodeCreated':
901 case 'ProvinceOrStateCreated':
903 case 'SublocationCreated':
905 case 'SpecialInstructions':
910 case 'FixtureIdentifier':
912 case 'LocationDestCode':
914 case 'JPEGFileComment':
915 case 'iimSupplementalCategory':
916 case 'OriginalTransmissionRef':
918 case 'dc-contributor':
927 case 'CameraOwnerName':
930 case 'RightsCertificate':
931 case 'CopyrightOwner':
934 case 'OriginalDocumentID':
936 case 'MorePermissionsUrl':
937 case 'AttributionUrl':
938 case 'PreferredAttributionName':
939 case 'PNGFileComment':
941 case 'ContentWarning':
942 case 'GIFFileComment':
944 case 'IntellectualGenre':
946 case 'OrginisationInImage':
947 case 'PersonInImage':
949 $val = htmlspecialchars( $val );
957 $val = $this->
exifMsg( $tag, $val );
960 $val = htmlspecialchars( $val );
968 $val = $this->
exifMsg( $tag, $val );
973 if ( $val ==
'-1' ) {
974 $val = $this->
exifMsg( $tag,
'rejected' );
981 $lang = Language::fetchLanguageName( strtolower( $val ), $this->
getLanguage()->getCode() );
982 $val = htmlspecialchars(
$lang ?: $val );
1019 $context->setLanguage( MediaWikiServices::getInstance()->getContentLanguage() );
1022 return $obj->flattenArrayReal( $vals,
$type, $noHtml );
1042 if ( !is_array( $vals ) ) {
1046 if ( isset( $vals[
'_type'] ) ) {
1047 $type = $vals[
'_type'];
1048 unset( $vals[
'_type'] );
1051 if ( !is_array( $vals ) ) {
1053 } elseif ( count( $vals ) === 1 &&
$type !==
'lang' && isset( $vals[0] ) ) {
1055 } elseif ( count( $vals ) === 0 ) {
1056 wfDebug( __METHOD__ .
" metadata array with 0 elements!\n" );
1075 $defaultItem =
false;
1076 $defaultLang =
false;
1085 if ( isset( $vals[
'x-default'] ) ) {
1086 $defaultItem = $vals[
'x-default'];
1087 unset( $vals[
'x-default'] );
1089 foreach ( $priorityLanguages as $pLang ) {
1090 if ( isset( $vals[$pLang] ) ) {
1092 if ( $vals[$pLang] === $defaultItem ) {
1093 $defaultItem =
false;
1097 $vals[$pLang], $pLang,
1098 $isDefault, $noHtml );
1100 unset( $vals[$pLang] );
1102 if ( $this->singleLang ) {
1103 return Html::rawElement(
'span',
1104 [
'lang' => $pLang ], $vals[$pLang] );
1110 foreach ( $vals as
$lang => $item ) {
1111 if ( $item === $defaultItem ) {
1112 $defaultLang =
$lang;
1116 $lang,
false, $noHtml );
1117 if ( $this->singleLang ) {
1118 return Html::rawElement(
'span',
1119 [
'lang' =>
$lang ], $item );
1122 if ( $defaultItem !==
false ) {
1124 $defaultLang,
true, $noHtml ) .
1126 if ( $this->singleLang ) {
1127 return $defaultItem;
1134 return '<ul class="metadata-langlist">' .
1139 return "\n#" . implode(
"\n#", $vals );
1142 return "<ol><li>" . implode(
"</li>\n<li>", $vals ) .
'</li></ol>';
1146 return "\n*" . implode(
"\n*", $vals );
1149 return "<ul><li>" . implode(
"</li>\n<li>", $vals ) .
'</li></ul>';
1165 if (
$lang ===
false && $default ===
false ) {
1166 throw new MWException(
'$lang and $default cannot both '
1171 $wrappedValue = $value;
1173 $wrappedValue =
'<span class="mw-metadata-lang-value">'
1174 . $value .
'</span>';
1177 if (
$lang ===
false ) {
1178 $msg = $this->
msg(
'metadata-langitem-default', $wrappedValue );
1180 return $msg->text() .
"\n\n";
1183 return '<li class="mw-metadata-lang-default">'
1188 $lowLang = strtolower(
$lang );
1189 $langName = Language::fetchLanguageName( $lowLang );
1190 if ( $langName ===
'' ) {
1192 $langPrefix = explode(
'-', $lowLang, 2 )[0];
1193 $langName = Language::fetchLanguageName( $langPrefix );
1194 if ( $langName ===
'' ) {
1201 $msg = $this->
msg(
'metadata-langitem', $wrappedValue, $langName,
$lang );
1203 return '*' . $msg->text();
1206 $item =
'<li class="mw-metadata-lang-code-'
1209 $item .=
' mw-metadata-lang-default';
1211 $item .=
'" lang="' .
$lang .
'">';
1212 $item .= $msg->text();
1227 private function exifMsg( $tag, $val, $arg =
null, $arg2 =
null ) {
1228 if ( $val ===
'' ) {
1233 MediaWikiServices::getInstance()->getContentLanguage()->lc(
"exif-$tag-$val" ),
1249 if ( is_array( $num ) ) {
1251 foreach ( $num as $number ) {
1257 if ( preg_match(
'/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
1259 $newNum = $m[1] / $m[2];
1260 if ( $round !==
false ) {
1261 $newNum = round( $newNum, $round );
1267 return $this->
getLanguage()->formatNum( $newNum );
1269 if ( is_numeric( $num ) && $round !==
false ) {
1270 $num = round( $num, $round );
1285 if ( preg_match(
'/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
1286 $numerator = intval( $m[1] );
1287 $denominator = intval( $m[2] );
1288 $gcd = $this->
gcd( abs( $numerator ), $denominator );
1291 return $this->
formatNum( $numerator / $gcd ) .
'/' . $this->
formatNum( $denominator / $gcd );
1305 private function gcd( $a, $b ) {
1315 $remainder = $a % $b;
1338 if ( !preg_match(
'/^\d{8}$/D', $val ) ) {
1343 switch ( substr( $val, 0, 2 ) ) {
1396 if ( $cat !==
'' ) {
1397 $catMsg = $this->
exifMsg(
'iimcategory', $cat );
1398 $val = $this->
exifMsg(
'subjectnewscode',
'', $val, $catMsg );
1413 if ( !is_numeric( $coord ) ) {
1414 wfDebugLog(
'exif', __METHOD__ .
": \"$coord\" is not a number" );
1415 return (
string)$coord;
1421 if (
$type ===
'latitude' ) {
1423 } elseif (
$type ===
'longitude' ) {
1427 $nCoord = (float)$coord;
1428 if (
$type ===
'latitude' ) {
1430 } elseif (
$type ===
'longitude' ) {
1435 $deg = floor( $nCoord );
1436 $min = floor( ( $nCoord - $deg ) * 60 );
1437 $sec = round( ( ( $nCoord - $deg ) * 60 - $min ) * 60, 2 );
1444 return $this->
msg(
'exif-coordinate-format', $deg, $min, $sec, $ref, $coord )->text();
1462 if ( !( isset( $vals[
'CiAdrExtadr'] )
1463 || isset( $vals[
'CiAdrCity'] )
1464 || isset( $vals[
'CiAdrCtry'] )
1465 || isset( $vals[
'CiEmailWork'] )
1466 || isset( $vals[
'CiTelWork'] )
1467 || isset( $vals[
'CiAdrPcode'] )
1468 || isset( $vals[
'CiAdrRegion'] )
1469 || isset( $vals[
'CiUrlWork'] )
1480 foreach ( $vals as &$val ) {
1481 $val = htmlspecialchars( $val );
1489 $url = $tel = $street = $city = $country =
'';
1490 $email = $postal = $region =
'';
1499 if ( isset( $vals[
'CiAdrExtadr'] ) ) {
1502 $street =
'<span class="extended-address">'
1504 $vals[
'CiAdrExtadr'] )
1507 if ( isset( $vals[
'CiAdrCity'] ) ) {
1508 $city =
'<span class="locality">'
1509 . htmlspecialchars( $vals[
'CiAdrCity'] )
1512 if ( isset( $vals[
'CiAdrCtry'] ) ) {
1513 $country =
'<span class="country-name">'
1514 . htmlspecialchars( $vals[
'CiAdrCtry'] )
1517 if ( isset( $vals[
'CiEmailWork'] ) ) {
1520 $splitEmails = explode(
"\n", $vals[
'CiEmailWork'] );
1521 foreach ( $splitEmails as $e1 ) {
1523 foreach ( explode(
',', $e1 ) as $e2 ) {
1524 $finalEmail = trim( $e2 );
1525 if ( $finalEmail ==
',' || $finalEmail ==
'' ) {
1528 if ( strpos( $finalEmail,
'<' ) !==
false ) {
1531 $emails[] = $finalEmail;
1533 $emails[] =
'[mailto:'
1535 .
' <span class="email">'
1541 $email = implode(
', ', $emails );
1543 if ( isset( $vals[
'CiTelWork'] ) ) {
1544 $tel =
'<span class="tel">'
1545 . htmlspecialchars( $vals[
'CiTelWork'] )
1548 if ( isset( $vals[
'CiAdrPcode'] ) ) {
1549 $postal =
'<span class="postal-code">'
1551 $vals[
'CiAdrPcode'] )
1554 if ( isset( $vals[
'CiAdrRegion'] ) ) {
1556 $region =
'<span class="region">'
1558 $vals[
'CiAdrRegion'] )
1561 if ( isset( $vals[
'CiUrlWork'] ) ) {
1562 $url =
'<span class="url">'
1563 . htmlspecialchars( $vals[
'CiUrlWork'] )
1567 return $this->
msg(
'exif-contact-value', $email, $url,
1568 $street, $city, $region, $postal, $country,
1581 $lines = explode(
"\n",
wfMessage(
'metadata-fields' )->inContentLanguage()->text() );
1584 if ( preg_match(
'/^\\*\s*(.*?)\s*$/',
$line,
$matches ) ) {
1588 $fields = array_map(
'strtolower', $fields );
1601 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1604 if (
$file->isDeleted( File::DELETED_FILE ) ) {
1608 $cacheKey =
$cache->makeKey(
1609 'getExtendedMetadata',
1611 (
int)$this->singleLang,
1615 $cachedValue =
$cache->get( $cacheKey );
1618 && Hooks::run(
'ValidateExtendedMetadataCache', [ $cachedValue[
'timestamp'],
$file ] )
1620 $extendedMetadata = $cachedValue[
'data'];
1625 if ( $this->singleLang ) {
1634 $valueToCache = [
'data' => $extendedMetadata,
'timestamp' =>
wfTimestampNow() ];
1635 $cache->set( $cacheKey, $valueToCache, $maxCacheTime );
1638 return $extendedMetadata;
1656 return $file->getExtendedMetadata() ?: [];
1664 'value' => $uploadDate,
1665 'source' =>
'mediawiki-metadata',
1671 $text =
$title->getText();
1672 $pos = strrpos( $text,
'.' );
1675 $name = substr( $text, 0, $pos );
1680 $fileMetadata[
'ObjectName'] = [
1682 'source' =>
'mediawiki-metadata',
1686 return $fileMetadata;
1702 Hooks::run(
'GetExtendedMetadata', [
1710 $visible = array_flip( self::getVisibleFields() );
1711 foreach ( $extendedMetadata as $key => $value ) {
1712 if ( !isset( $visible[strtolower( $key )] ) ) {
1713 $extendedMetadata[$key][
'hidden'] =
'';
1717 return $extendedMetadata;
1731 || !isset( $value[
'_type'] )
1732 || $value[
'_type'] !=
'lang'
1739 foreach ( $priorityLanguages as
$lang ) {
1740 if ( isset( $value[
$lang] ) ) {
1741 return $value[
$lang];
1746 if ( isset( $value[
'x-default'] ) ) {
1747 return $value[
'x-default'];
1751 unset( $value[
'_type'] );
1752 if ( !empty( $value ) ) {
1753 return reset( $value );
1770 if ( !is_array( $value ) ) {
1772 } elseif ( isset( $value[
'_type'] ) && $value[
'_type'] ===
'lang' ) {
1775 foreach ( $value as $k => $v ) {
1780 $v = reset( $value );
1781 if ( key( $value ) ===
'_type' ) {
1782 $v = next( $value );
1795 if ( !is_array( $metadata ) ) {
1798 foreach ( $metadata as &$field ) {
1799 if ( isset( $field[
'value'] ) ) {
1812 if ( !is_array( $metadata ) ) {
1815 foreach ( $metadata as $key => &$field ) {
1816 if ( $key ===
'Software' || $key ===
'Contact' ) {
1821 if ( isset( $field[
'value'] ) ) {
1832 if ( !is_array( $arr ) ) {
1837 foreach ( $arr as $key => &$value ) {
1839 if ( $sanitizedKey !== $key ) {
1840 if ( isset( $arr[$sanitizedKey] ) ) {
1845 $sanitizedKey .= $counter;
1848 $arr[$sanitizedKey] = $arr[$key];
1849 unset( $arr[$key] );
1851 if ( is_array( $value ) ) {
1857 $keys = array_filter( array_keys( $arr ),
'ApiResult::isMetadataKey' );
1859 ApiResult::setPreserveKeysList( $arr,
$keys );
1873 $key = preg_replace(
'/[^a-zA-z0-9_:.\-]/',
'', $key );
1875 $key = preg_replace(
'/^[\d\-.]+/',
'', $key );
1882 if ( $key ==
'_element' ) {
1896 $priorityLanguages =
1897 Language::getFallbacksIncludingSiteLanguage( $this->
getLanguage()->getCode() );
1898 $priorityLanguages = array_merge(
1900 $priorityLanguages[0],
1901 $priorityLanguages[1]
1904 return $priorityLanguages;
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
setContext(IContextSource $context)
An IContextSource implementation which will inherit context from another source but allow individual ...
Implements some public methods and some protected utility functions which are required by multiple ch...
Foreign file accessible through api.php requests.
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
if(!isset( $args[0])) $lang