28use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
30use Wikimedia\Timestamp\TimestampException;
54 use ProtectedHookAccessorTrait;
70 $this->singleLang = $val;
92 return $obj->makeFormattedData( $tags );
107 $resolutionunit = !isset( $tags[
'ResolutionUnit'] ) || $tags[
'ResolutionUnit'] == 2 ? 2 : 3;
108 unset( $tags[
'ResolutionUnit'] );
110 foreach ( $tags as $tag => &$vals ) {
113 if ( !is_array( $tags[$tag] ) ) {
118 if ( isset( $tags[$tag][
'_type'] ) ) {
119 $type = $tags[$tag][
'_type'];
120 unset( $vals[
'_type'] );
126 if ( $tag ==
'GPSTimeStamp' && count( $vals ) === 3 ) {
129 $h = explode(
'/', $vals[0] );
130 $m = explode(
'/', $vals[1] );
131 $s = explode(
'/', $vals[2] );
146 $tags[$tag] = str_pad( intval( $h[0] / $h[1] ), 2,
'0', STR_PAD_LEFT )
147 .
':' . str_pad( intval( $m[0] / $m[1] ), 2,
'0', STR_PAD_LEFT )
148 .
':' . str_pad( intval(
$s[0] /
$s[1] ), 2,
'0', STR_PAD_LEFT );
151 $time =
wfTimestamp( TS_MW,
'1971:01:01 ' . $tags[$tag] );
153 if ( $time && intval( $time ) > 0 ) {
156 }
catch ( TimestampException $e ) {
167 if ( $tag ===
'Contact' ) {
172 foreach ( $vals as &$val ) {
187 $val = $this->
exifMsg( $tag, $val );
195 case 'PhotometricInterpretation':
209 $val = $this->
exifMsg( $tag, $val );
227 $val = $this->
exifMsg( $tag, $val );
235 case 'PlanarConfiguration':
239 $val = $this->
exifMsg( $tag, $val );
248 case 'YCbCrPositioning':
252 $val = $this->
exifMsg( $tag, $val );
262 switch ( $resolutionunit ) {
282 case 'FlashPixVersion':
283 $val = (int)$val / 100;
290 $val = $this->
exifMsg( $tag, $val );
298 case 'ComponentsConfiguration':
307 $val = $this->
exifMsg( $tag, $val );
316 case 'DateTimeOriginal':
317 case 'DateTimeDigitized':
318 case 'DateTimeReleased':
319 case 'DateTimeExpires':
322 case 'DateTimeMetadata':
323 if ( $val ==
'0000:00:00 00:00:00' || $val ==
' : : : : ' ) {
324 $val = $this->
msg(
'exif-unknowndate' )->text();
325 } elseif ( preg_match(
326 '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/D',
331 if ( $time && intval( $time ) > 0 ) {
334 } elseif ( preg_match(
'/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d)$/D', $val ) ) {
339 if ( $time && intval( $time ) > 0 ) {
342 } elseif ( preg_match(
'/^(?:\d{4}):(?:\d\d):(?:\d\d)$/D', $val ) ) {
345 . substr( $val, 5, 2 )
346 . substr( $val, 8, 2 )
348 if ( $time && intval( $time ) > 0 ) {
355 case 'ExposureProgram':
366 $val = $this->
exifMsg( $tag, $val );
374 case 'SubjectDistance':
389 $val = $this->
exifMsg( $tag, $val );
420 $val = $this->
exifMsg( $tag, $val );
430 'fired' => $val & 0b00000001,
431 'return' => ( $val & 0b00000110 ) >> 1,
432 'mode' => ( $val & 0b00011000 ) >> 3,
433 'function' => ( $val & 0b00100000 ) >> 5,
434 'redeye' => ( $val & 0b01000000 ) >> 6,
438 # We do not need to handle unknown values since all are used.
439 foreach ( $flashDecode as $subTag => $subValue ) {
440 # We do not need any message for zeroed values.
441 if ( $subTag !=
'fired' && $subValue == 0 ) {
444 $fullTag = $tag .
'-' . $subTag;
445 $flashMsgs[] = $this->
exifMsg( $fullTag, $subValue );
447 $val = $this->
getLanguage()->commaList( $flashMsgs );
450 case 'FocalPlaneResolutionUnit':
453 $val = $this->
exifMsg( $tag, $val );
461 case 'SensingMethod':
470 $val = $this->
exifMsg( $tag, $val );
481 $val = $this->
exifMsg( $tag, $val );
492 $val = $this->
exifMsg( $tag, $val );
500 case 'CustomRendered':
512 $val = $this->
exifMsg( $tag, $val );
525 $val = $this->
exifMsg( $tag, $val );
537 $val = $this->
exifMsg( $tag, $val );
545 case 'SceneCaptureType':
551 $val = $this->
exifMsg( $tag, $val );
566 $val = $this->
exifMsg( $tag, $val );
579 $val = $this->
exifMsg( $tag, $val );
592 $val = $this->
exifMsg( $tag, $val );
605 $val = $this->
exifMsg( $tag, $val );
613 case 'SubjectDistanceRange':
619 $val = $this->
exifMsg( $tag, $val );
628 case 'GPSLatitudeRef':
629 case 'GPSDestLatitudeRef':
633 $val = $this->
exifMsg(
'GPSLatitude', $val );
641 case 'GPSLongitudeRef':
642 case 'GPSDestLongitudeRef':
646 $val = $this->
exifMsg(
'GPSLongitude', $val );
656 $val = $this->
exifMsg(
'GPSAltitude',
'below-sealevel', $this->
formatNum( -$val, 3 ) );
658 $val = $this->
exifMsg(
'GPSAltitude',
'above-sealevel', $this->
formatNum( $val, 3 ) );
666 $val = $this->
exifMsg( $tag, $val );
674 case 'GPSMeasureMode':
678 $val = $this->
exifMsg( $tag, $val );
687 case 'GPSImgDirectionRef':
688 case 'GPSDestBearingRef':
692 $val = $this->
exifMsg(
'GPSDirection', $val );
701 case 'GPSDestLatitude':
705 case 'GPSDestLongitude':
714 $val = $this->
exifMsg(
'GPSSpeed', $val );
722 case 'GPSDestDistanceRef':
727 $val = $this->
exifMsg(
'GPSDestDistance', $val );
739 } elseif ( $val <= 5 ) {
741 } elseif ( $val <= 10 ) {
743 } elseif ( $val <= 20 ) {
755 $val = $this->
exifMsg( $tag,
'', $val );
759 if ( is_array( $val ) ) {
760 if ( count( $val ) > 1 ) {
762 $val = $this->
msg(
'exif-software-version-value', $val[0], $val[1] )->text();
765 $val = $this->
exifMsg( $tag,
'', $val[0] );
768 $val = $this->
exifMsg( $tag,
'', $val );
774 $val = $this->
msg(
'exif-exposuretime-format',
777 case 'ISOSpeedRatings':
781 if ( $val ==
'65535' ) {
782 $val = $this->
exifMsg( $tag,
'overflow' );
788 $val = $this->
msg(
'exif-fnumber-format',
793 case 'FocalLengthIn35mmFilm':
794 $val = $this->
msg(
'exif-focallength-format',
798 case 'MaxApertureValue':
799 if ( strpos( $val,
'/' ) !==
false ) {
801 list( $n, $d ) = explode(
'/', $val );
802 if ( is_numeric( $n ) && is_numeric( $d ) ) {
806 if ( is_numeric( $val ) ) {
807 $fNumber = 2 ** ( $val / 2 );
808 if ( is_finite( $fNumber ) ) {
809 $val = $this->
msg(
'exif-maxaperturevalue-value',
818 switch ( strtolower( $val ) ) {
844 case 'SubjectNewsCode':
856 if ( $val == 0 || $val == 9 ) {
858 } elseif ( $val < 5 && $val > 1 ) {
860 } elseif ( $val == 5 ) {
862 } elseif ( $val <= 8 && $val > 5 ) {
866 if ( $urgency !==
'' ) {
867 $val = $this->
exifMsg(
'urgency',
874 case 'OriginalImageHeight':
875 case 'OriginalImageWidth':
876 case 'PixelXDimension':
877 case 'PixelYDimension':
880 $val = $this->
formatNum( $val ) .
' ' . $this->
msg(
'unit-pixel' )->text();
890 case 'ImageDescription':
894 case 'RelatedSoundFile':
895 case 'ImageUniqueID':
896 case 'SpectralSensitivity':
897 case 'GPSSatellites':
901 case 'WorldRegionDest':
903 case 'CountryCodeDest':
904 case 'ProvinceOrStateDest':
906 case 'SublocationDest':
907 case 'WorldRegionCreated':
908 case 'CountryCreated':
909 case 'CountryCodeCreated':
910 case 'ProvinceOrStateCreated':
912 case 'SublocationCreated':
914 case 'SpecialInstructions':
919 case 'FixtureIdentifier':
921 case 'LocationDestCode':
923 case 'JPEGFileComment':
924 case 'iimSupplementalCategory':
925 case 'OriginalTransmissionRef':
927 case 'dc-contributor':
936 case 'CameraOwnerName':
939 case 'RightsCertificate':
940 case 'CopyrightOwner':
943 case 'OriginalDocumentID':
945 case 'MorePermissionsUrl':
946 case 'AttributionUrl':
947 case 'PreferredAttributionName':
948 case 'PNGFileComment':
950 case 'ContentWarning':
951 case 'GIFFileComment':
953 case 'IntellectualGenre':
955 case 'OrginisationInImage':
956 case 'PersonInImage':
957 $val = htmlspecialchars( $val );
965 $val = $this->
exifMsg( $tag, $val );
968 $val = htmlspecialchars( $val );
976 $val = $this->
exifMsg( $tag, $val );
981 if ( $val ==
'-1' ) {
982 $val = $this->
exifMsg( $tag,
'rejected' );
989 $lang = MediaWikiServices::getInstance()
990 ->getLanguageNameUtils()
991 ->getLanguageName( strtolower( $val ), $this->
getLanguage()->getCode() );
992 $val = htmlspecialchars(
$lang ?: $val );
1029 $context->setLanguage( MediaWikiServices::getInstance()->getContentLanguage() );
1032 return $obj->flattenArrayReal( $vals,
$type, $noHtml );
1052 if ( !is_array( $vals ) ) {
1056 if ( isset( $vals[
'_type'] ) ) {
1057 $type = $vals[
'_type'];
1058 unset( $vals[
'_type'] );
1061 if ( count( $vals ) === 1 &&
$type !==
'lang' && isset( $vals[0] ) ) {
1063 } elseif ( count( $vals ) === 0 ) {
1064 wfDebug( __METHOD__ .
" metadata array with 0 elements!" );
1083 $defaultItem =
false;
1084 $defaultLang =
false;
1093 if ( isset( $vals[
'x-default'] ) ) {
1094 $defaultItem = $vals[
'x-default'];
1095 unset( $vals[
'x-default'] );
1097 foreach ( $priorityLanguages as $pLang ) {
1098 if ( isset( $vals[$pLang] ) ) {
1100 if ( $vals[$pLang] === $defaultItem ) {
1101 $defaultItem =
false;
1105 $vals[$pLang], $pLang,
1106 $isDefault, $noHtml );
1108 unset( $vals[$pLang] );
1110 if ( $this->singleLang ) {
1111 return Html::rawElement(
'span',
1112 [
'lang' => $pLang ], $vals[$pLang] );
1118 foreach ( $vals as
$lang => $item ) {
1119 if ( $item === $defaultItem ) {
1120 $defaultLang =
$lang;
1124 $lang,
false, $noHtml );
1125 if ( $this->singleLang ) {
1126 return Html::rawElement(
'span',
1127 [
'lang' =>
$lang ], $item );
1130 if ( $defaultItem !==
false ) {
1132 $defaultLang,
true, $noHtml ) .
1134 if ( $this->singleLang ) {
1135 return $defaultItem;
1142 return '<ul class="metadata-langlist">' .
1147 return "\n#" . implode(
"\n#", $vals );
1150 return "<ol><li>" . implode(
"</li>\n<li>", $vals ) .
'</li></ol>';
1154 return "\n*" . implode(
"\n*", $vals );
1157 return "<ul><li>" . implode(
"</li>\n<li>", $vals ) .
'</li></ul>';
1173 if (
$lang ===
false && $default ===
false ) {
1174 throw new MWException(
'$lang and $default cannot both '
1179 $wrappedValue = $value;
1181 $wrappedValue =
'<span class="mw-metadata-lang-value">'
1182 . $value .
'</span>';
1185 if (
$lang ===
false ) {
1186 $msg = $this->
msg(
'metadata-langitem-default', $wrappedValue );
1188 return $msg->text() .
"\n\n";
1191 return '<li class="mw-metadata-lang-default">'
1196 $lowLang = strtolower(
$lang );
1197 $languageNameUtils = MediaWikiServices::getInstance()->getLanguageNameUtils();
1198 $langName = $languageNameUtils->getLanguageName( $lowLang );
1199 if ( $langName ===
'' ) {
1201 $langPrefix = explode(
'-', $lowLang, 2 )[0];
1202 $langName = $languageNameUtils->getLanguageName( $langPrefix );
1203 if ( $langName ===
'' ) {
1210 $msg = $this->
msg(
'metadata-langitem', $wrappedValue, $langName,
$lang );
1212 return '*' . $msg->text();
1215 $item =
'<li class="mw-metadata-lang-code-'
1218 $item .=
' mw-metadata-lang-default';
1220 $item .=
'" lang="' .
$lang .
'">';
1221 $item .= $msg->text();
1236 private function exifMsg( $tag, $val, $arg =
null, $arg2 =
null ) {
1237 if ( $val ===
'' ) {
1242 MediaWikiServices::getInstance()->getContentLanguage()->lc(
"exif-$tag-$val" ),
1258 if ( is_array( $num ) ) {
1260 foreach ( $num as $number ) {
1266 if ( preg_match(
'/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
1268 $newNum = $m[1] / $m[2];
1269 if ( $round !==
false ) {
1270 $newNum = round( $newNum, $round );
1276 return $this->
getLanguage()->formatNum( $newNum );
1278 if ( is_numeric( $num ) && $round !==
false ) {
1279 $num = round( $num, $round );
1294 if ( preg_match(
'/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
1295 $numerator = intval( $m[1] );
1296 $denominator = intval( $m[2] );
1297 $gcd = $this->
gcd( abs( $numerator ), $denominator );
1300 return $this->
formatNum( $numerator / $gcd ) .
'/' . $this->
formatNum( $denominator / $gcd );
1314 private function gcd( $a, $b ) {
1324 $remainder = $a % $b;
1347 if ( !preg_match(
'/^\d{8}$/D', $val ) ) {
1352 switch ( substr( $val, 0, 2 ) ) {
1405 if ( $cat !==
'' ) {
1406 $catMsg = $this->
exifMsg(
'iimcategory', $cat );
1407 $val = $this->
exifMsg(
'subjectnewscode',
'', $val, $catMsg );
1422 if ( !is_numeric( $coord ) ) {
1423 wfDebugLog(
'exif', __METHOD__ .
": \"$coord\" is not a number" );
1424 return (
string)$coord;
1430 if (
$type ===
'latitude' ) {
1432 } elseif (
$type ===
'longitude' ) {
1436 $nCoord = (float)$coord;
1437 if (
$type ===
'latitude' ) {
1439 } elseif (
$type ===
'longitude' ) {
1444 $deg = floor( $nCoord );
1445 $min = floor( ( $nCoord - $deg ) * 60 );
1446 $sec = round( ( ( $nCoord - $deg ) * 60 - $min ) * 60, 2 );
1453 return $this->
msg(
'exif-coordinate-format', $deg, $min, $sec, $ref, $coord )->text();
1471 if ( !( isset( $vals[
'CiAdrExtadr'] )
1472 || isset( $vals[
'CiAdrCity'] )
1473 || isset( $vals[
'CiAdrCtry'] )
1474 || isset( $vals[
'CiEmailWork'] )
1475 || isset( $vals[
'CiTelWork'] )
1476 || isset( $vals[
'CiAdrPcode'] )
1477 || isset( $vals[
'CiAdrRegion'] )
1478 || isset( $vals[
'CiUrlWork'] )
1489 foreach ( $vals as &$val ) {
1490 $val = htmlspecialchars( $val );
1498 $url = $tel = $street = $city = $country =
'';
1499 $email = $postal = $region =
'';
1508 if ( isset( $vals[
'CiAdrExtadr'] ) ) {
1511 $street =
'<span class="extended-address">'
1513 $vals[
'CiAdrExtadr'] )
1516 if ( isset( $vals[
'CiAdrCity'] ) ) {
1517 $city =
'<span class="locality">'
1518 . htmlspecialchars( $vals[
'CiAdrCity'] )
1521 if ( isset( $vals[
'CiAdrCtry'] ) ) {
1522 $country =
'<span class="country-name">'
1523 . htmlspecialchars( $vals[
'CiAdrCtry'] )
1526 if ( isset( $vals[
'CiEmailWork'] ) ) {
1529 $splitEmails = explode(
"\n", $vals[
'CiEmailWork'] );
1530 foreach ( $splitEmails as $e1 ) {
1532 foreach ( explode(
',', $e1 ) as $e2 ) {
1533 $finalEmail = trim( $e2 );
1534 if ( $finalEmail ==
',' || $finalEmail ==
'' ) {
1537 if ( strpos( $finalEmail,
'<' ) !==
false ) {
1540 $emails[] = $finalEmail;
1542 $emails[] =
'[mailto:'
1544 .
' <span class="email">'
1550 $email = implode(
', ', $emails );
1552 if ( isset( $vals[
'CiTelWork'] ) ) {
1553 $tel =
'<span class="tel">'
1554 . htmlspecialchars( $vals[
'CiTelWork'] )
1557 if ( isset( $vals[
'CiAdrPcode'] ) ) {
1558 $postal =
'<span class="postal-code">'
1560 $vals[
'CiAdrPcode'] )
1563 if ( isset( $vals[
'CiAdrRegion'] ) ) {
1565 $region =
'<span class="region">'
1567 $vals[
'CiAdrRegion'] )
1570 if ( isset( $vals[
'CiUrlWork'] ) ) {
1571 $url =
'<span class="url">'
1572 . htmlspecialchars( $vals[
'CiUrlWork'] )
1576 return $this->
msg(
'exif-contact-value', $email, $url,
1577 $street, $city, $region, $postal, $country,
1590 $lines = explode(
"\n",
wfMessage(
'metadata-fields' )->inContentLanguage()->text() );
1593 if ( preg_match(
'/^\\*\s*(.*?)\s*$/',
$line,
$matches ) ) {
1597 $fields = array_map(
'strtolower', $fields );
1610 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1613 if (
$file->isDeleted( File::DELETED_FILE ) ) {
1617 $cacheKey =
$cache->makeKey(
1618 'getExtendedMetadata',
1620 (
int)$this->singleLang,
1624 $cachedValue =
$cache->get( $cacheKey );
1627 && $this->getHookRunner()->onValidateExtendedMetadataCache( $cachedValue[
'timestamp'],
$file )
1629 $extendedMetadata = $cachedValue[
'data'];
1634 if ( $this->singleLang ) {
1643 $valueToCache = [
'data' => $extendedMetadata,
'timestamp' =>
wfTimestampNow() ];
1644 $cache->set( $cacheKey, $valueToCache, $maxCacheTime );
1647 return $extendedMetadata;
1665 return $file->getExtendedMetadata() ?: [];
1673 'value' => $uploadDate,
1674 'source' =>
'mediawiki-metadata',
1680 $text =
$title->getText();
1681 $pos = strrpos( $text,
'.' );
1684 $name = substr( $text, 0, $pos );
1689 $fileMetadata[
'ObjectName'] = [
1691 'source' =>
'mediawiki-metadata',
1695 return $fileMetadata;
1711 $this->getHookRunner()->onGetExtendedMetadata(
1719 $visible = array_flip( self::getVisibleFields() );
1720 foreach ( $extendedMetadata as $key => $value ) {
1721 if ( !isset( $visible[strtolower( $key )] ) ) {
1722 $extendedMetadata[$key][
'hidden'] =
'';
1726 return $extendedMetadata;
1740 || !isset( $value[
'_type'] )
1741 || $value[
'_type'] !=
'lang'
1748 foreach ( $priorityLanguages as
$lang ) {
1749 if ( isset( $value[
$lang] ) ) {
1750 return $value[
$lang];
1755 if ( isset( $value[
'x-default'] ) ) {
1756 return $value[
'x-default'];
1760 unset( $value[
'_type'] );
1761 if ( !empty( $value ) ) {
1762 return reset( $value );
1779 if ( !is_array( $value ) ) {
1781 } elseif ( isset( $value[
'_type'] ) && $value[
'_type'] ===
'lang' ) {
1784 foreach ( $value as $k => $v ) {
1789 $v = reset( $value );
1790 if ( key( $value ) ===
'_type' ) {
1791 $v = next( $value );
1804 if ( !is_array( $metadata ) ) {
1807 foreach ( $metadata as &$field ) {
1808 if ( isset( $field[
'value'] ) ) {
1821 if ( !is_array( $metadata ) ) {
1824 foreach ( $metadata as $key => &$field ) {
1825 if ( $key ===
'Software' || $key ===
'Contact' ) {
1830 if ( isset( $field[
'value'] ) ) {
1841 if ( !is_array( $arr ) ) {
1846 foreach ( $arr as $key => &$value ) {
1848 if ( $sanitizedKey !== $key ) {
1849 if ( isset( $arr[$sanitizedKey] ) ) {
1854 $sanitizedKey .= $counter;
1857 $arr[$sanitizedKey] = $arr[$key];
1858 unset( $arr[$key] );
1860 if ( is_array( $value ) ) {
1866 $keys = array_filter( array_keys( $arr ),
'ApiResult::isMetadataKey' );
1868 ApiResult::setPreserveKeysList( $arr,
$keys );
1882 $key = preg_replace(
'/[^a-zA-z0-9_:.\-]/',
'', $key );
1884 $key = preg_replace(
'/^[\d\-.]+/',
'', $key );
1891 if ( $key ==
'_element' ) {
1905 $priorityLanguages = MediaWikiServices::getInstance()
1906 ->getLanguageFallback()
1907 ->getAllIncludingSiteLanguage( $this->
getLanguage()->getCode() );
1908 $priorityLanguages = array_merge(
1910 $priorityLanguages[0],
1911 $priorityLanguages[1]
1914 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()
getContext()
Get the base IContextSource object.
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
if(!file_exists( $CREDITS)) $lines