MediaWiki  REL1_21
FormatMetadata.php
Go to the documentation of this file.
00001 <?php
00047 class FormatMetadata {
00048 
00059         public static function getFormattedData( $tags ) {
00060                 global $wgLang;
00061 
00062                 $resolutionunit = !isset( $tags['ResolutionUnit'] ) || $tags['ResolutionUnit'] == 2 ? 2 : 3;
00063                 unset( $tags['ResolutionUnit'] );
00064 
00065                 foreach ( $tags as $tag => &$vals ) {
00066 
00067                         // This seems ugly to wrap non-array's in an array just to unwrap again,
00068                         // especially when most of the time it is not an array
00069                         if ( !is_array( $tags[$tag] ) ) {
00070                                 $vals = Array( $vals );
00071                         }
00072 
00073                         // _type is a special value to say what array type
00074                         if ( isset( $tags[$tag]['_type'] ) ) {
00075                                 $type = $tags[$tag]['_type'];
00076                                 unset( $vals['_type'] );
00077                         } else {
00078                                 $type = 'ul'; // default unordered list.
00079                         }
00080 
00081                         //This is done differently as the tag is an array.
00082                         if ( $tag == 'GPSTimeStamp' && count( $vals ) === 3) {
00083                                 //hour min sec array
00084 
00085                                 $h = explode( '/', $vals[0] );
00086                                 $m = explode( '/', $vals[1] );
00087                                 $s = explode( '/', $vals[2] );
00088 
00089                                 // this should already be validated
00090                                 // when loaded from file, but it could
00091                                 // come from a foreign repo, so be
00092                                 // paranoid.
00093                                 if ( !isset( $h[1] )
00094                                         || !isset( $m[1] )
00095                                         || !isset( $s[1] )
00096                                         || $h[1] == 0
00097                                         || $m[1] == 0
00098                                         || $s[1] == 0
00099                                 ) {
00100                                         continue;
00101                                 }
00102                                 $tags[$tag] = str_pad( intval( $h[0] / $h[1] ), 2, '0', STR_PAD_LEFT )
00103                                         . ':' . str_pad( intval( $m[0] / $m[1] ), 2, '0', STR_PAD_LEFT )
00104                                         . ':' . str_pad( intval( $s[0] / $s[1] ), 2, '0', STR_PAD_LEFT );
00105 
00106                                 try {
00107                                         $time = wfTimestamp( TS_MW, '1971:01:01 ' . $tags[$tag] );
00108                                         // the 1971:01:01 is just a placeholder, and not shown to user.
00109                                         if ( $time && intval( $time ) > 0 ) {
00110                                                 $tags[$tag] = $wgLang->time( $time );
00111                                         }
00112                                 } catch ( TimestampException $e ) {
00113                                         // This shouldn't happen, but we've seen bad formats
00114                                         // such as 4-digit seconds in the wild.
00115                                         // leave $tags[$tag] as-is
00116                                 }
00117                                 continue;
00118                         }
00119 
00120                         // The contact info is a multi-valued field
00121                         // instead of the other props which are single
00122                         // valued (mostly) so handle as a special case.
00123                         if ( $tag === 'Contact' ) {
00124                                 $vals = self::collapseContactInfo( $vals );
00125                                 continue;
00126                         }
00127 
00128                         foreach ( $vals as &$val ) {
00129 
00130                                 switch( $tag ) {
00131                                 case 'Compression':
00132                                         switch( $val ) {
00133                                         case 1: case 2: case 3: case 4:
00134                                         case 5: case 6: case 7: case 8:
00135                                         case 32773: case 32946: case 34712:
00136                                                 $val = self::msg( $tag, $val );
00137                                                 break;
00138                                         default:
00139                                                 /* If not recognized, display as is. */
00140                                                 break;
00141                                         }
00142                                         break;
00143 
00144                                 case 'PhotometricInterpretation':
00145                                         switch( $val ) {
00146                                         case 2: case 6:
00147                                                 $val = self::msg( $tag, $val );
00148                                                 break;
00149                                         default:
00150                                                 /* If not recognized, display as is. */
00151                                                 break;
00152                                         }
00153                                         break;
00154 
00155                                 case 'Orientation':
00156                                         switch( $val ) {
00157                                         case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
00158                                                 $val = self::msg( $tag, $val );
00159                                                 break;
00160                                         default:
00161                                                 /* If not recognized, display as is. */
00162                                                 break;
00163                                         }
00164                                         break;
00165 
00166                                 case 'PlanarConfiguration':
00167                                         switch( $val ) {
00168                                         case 1: case 2:
00169                                                 $val = self::msg( $tag, $val );
00170                                                 break;
00171                                         default:
00172                                                 /* If not recognized, display as is. */
00173                                                 break;
00174                                         }
00175                                         break;
00176 
00177                                 // TODO: YCbCrSubSampling
00178                                 case 'YCbCrPositioning':
00179                                         switch ( $val ) {
00180                                         case 1:
00181                                         case 2:
00182                                                 $val = self::msg( $tag, $val );
00183                                                 break;
00184                                         default:
00185                                                 /* If not recognized, display as is. */
00186                                                 break;
00187                                         }
00188                                         break;
00189 
00190                                 case 'XResolution':
00191                                 case 'YResolution':
00192                                         switch( $resolutionunit ) {
00193                                                 case 2:
00194                                                         $val = self::msg( 'XYResolution', 'i', self::formatNum( $val ) );
00195                                                         break;
00196                                                 case 3:
00197                                                         $val = self::msg( 'XYResolution', 'c', self::formatNum( $val ) );
00198                                                         break;
00199                                                 default:
00200                                                         /* If not recognized, display as is. */
00201                                                         break;
00202                                         }
00203                                         break;
00204 
00205                                 // TODO: YCbCrCoefficients  #p27 (see annex E)
00206                                 case 'ExifVersion': case 'FlashpixVersion':
00207                                         $val = "$val" / 100;
00208                                         break;
00209 
00210                                 case 'ColorSpace':
00211                                         switch( $val ) {
00212                                         case 1: case 65535:
00213                                                 $val = self::msg( $tag, $val );
00214                                                 break;
00215                                         default:
00216                                                 /* If not recognized, display as is. */
00217                                                 break;
00218                                         }
00219                                         break;
00220 
00221                                 case 'ComponentsConfiguration':
00222                                         switch( $val ) {
00223                                         case 0: case 1: case 2: case 3: case 4: case 5: case 6:
00224                                                 $val = self::msg( $tag, $val );
00225                                                 break;
00226                                         default:
00227                                                 /* If not recognized, display as is. */
00228                                                 break;
00229                                         }
00230                                         break;
00231 
00232                                 case 'DateTime':
00233                                 case 'DateTimeOriginal':
00234                                 case 'DateTimeDigitized':
00235                                 case 'DateTimeReleased':
00236                                 case 'DateTimeExpires':
00237                                 case 'GPSDateStamp':
00238                                 case 'dc-date':
00239                                 case 'DateTimeMetadata':
00240                                         if ( $val == '0000:00:00 00:00:00' || $val == '    :  :     :  :  ' ) {
00241                                                 $val = wfMessage( 'exif-unknowndate' )->text();
00242                                         } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/D', $val ) ) {
00243                                                 // Full date.
00244                                                 $time = wfTimestamp( TS_MW, $val );
00245                                                 if ( $time && intval( $time ) > 0 ) {
00246                                                         $val = $wgLang->timeanddate( $time );
00247                                                 }
00248                                         } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d)$/D', $val ) ) {
00249                                                 // No second field. Still format the same
00250                                                 // since timeanddate doesn't include seconds anyways,
00251                                                 // but second still available in api
00252                                                 $time = wfTimestamp( TS_MW, $val . ':00' );
00253                                                 if ( $time && intval( $time ) > 0 ) {
00254                                                         $val = $wgLang->timeanddate( $time );
00255                                                 }
00256                                         } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d)$/D', $val ) ) {
00257                                                 // If only the date but not the time is filled in.
00258                                                 $time = wfTimestamp( TS_MW, substr( $val, 0, 4 )
00259                                                         . substr( $val, 5, 2 )
00260                                                         . substr( $val, 8, 2 )
00261                                                         . '000000' );
00262                                                 if ( $time && intval( $time ) > 0 ) {
00263                                                         $val = $wgLang->date( $time );
00264                                                 }
00265                                         }
00266                                         // else it will just output $val without formatting it.
00267                                         break;
00268 
00269                                 case 'ExposureProgram':
00270                                         switch( $val ) {
00271                                         case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
00272                                                 $val = self::msg( $tag, $val );
00273                                                 break;
00274                                         default:
00275                                                 /* If not recognized, display as is. */
00276                                                 break;
00277                                         }
00278                                         break;
00279 
00280                                 case 'SubjectDistance':
00281                                         $val = self::msg( $tag, '', self::formatNum( $val ) );
00282                                         break;
00283 
00284                                 case 'MeteringMode':
00285                                         switch( $val ) {
00286                                         case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 255:
00287                                                 $val = self::msg( $tag, $val );
00288                                                 break;
00289                                         default:
00290                                                 /* If not recognized, display as is. */
00291                                                 break;
00292                                         }
00293                                         break;
00294 
00295                                 case 'LightSource':
00296                                         switch( $val ) {
00297                                         case 0: case 1: case 2: case 3: case 4: case 9: case 10: case 11:
00298                                         case 12: case 13: case 14: case 15: case 17: case 18: case 19: case 20:
00299                                         case 21: case 22: case 23: case 24: case 255:
00300                                                 $val = self::msg( $tag, $val );
00301                                                 break;
00302                                         default:
00303                                                 /* If not recognized, display as is. */
00304                                                 break;
00305                                         }
00306                                         break;
00307 
00308                                 case 'Flash':
00309                                         $flashDecode = array(
00310                                                 'fired'    => $val & bindec( '00000001' ),
00311                                                 'return'   => ( $val & bindec( '00000110' ) ) >> 1,
00312                                                 'mode'     => ( $val & bindec( '00011000' ) ) >> 3,
00313                                                 'function' => ( $val & bindec( '00100000' ) ) >> 5,
00314                                                 'redeye'   => ( $val & bindec( '01000000' ) ) >> 6,
00315 //                                              'reserved' => ($val & bindec( '10000000' )) >> 7,
00316                                         );
00317                                         $flashMsgs = array();
00318                                         # We do not need to handle unknown values since all are used.
00319                                         foreach ( $flashDecode as $subTag => $subValue ) {
00320                                                 # We do not need any message for zeroed values.
00321                                                 if ( $subTag != 'fired' && $subValue == 0 ) {
00322                                                         continue;
00323                                                 }
00324                                                 $fullTag = $tag . '-' . $subTag;
00325                                                 $flashMsgs[] = self::msg( $fullTag, $subValue );
00326                                         }
00327                                         $val = $wgLang->commaList( $flashMsgs );
00328                                         break;
00329 
00330                                 case 'FocalPlaneResolutionUnit':
00331                                         switch( $val ) {
00332                                         case 2:
00333                                                 $val = self::msg( $tag, $val );
00334                                                 break;
00335                                         default:
00336                                                 /* If not recognized, display as is. */
00337                                                 break;
00338                                         }
00339                                         break;
00340 
00341                                 case 'SensingMethod':
00342                                         switch( $val ) {
00343                                         case 1: case 2: case 3: case 4: case 5: case 7: case 8:
00344                                                 $val = self::msg( $tag, $val );
00345                                                 break;
00346                                         default:
00347                                                 /* If not recognized, display as is. */
00348                                                 break;
00349                                         }
00350                                         break;
00351 
00352                                 case 'FileSource':
00353                                         switch( $val ) {
00354                                         case 3:
00355                                                 $val = self::msg( $tag, $val );
00356                                                 break;
00357                                         default:
00358                                                 /* If not recognized, display as is. */
00359                                                 break;
00360                                         }
00361                                         break;
00362 
00363                                 case 'SceneType':
00364                                         switch( $val ) {
00365                                         case 1:
00366                                                 $val = self::msg( $tag, $val );
00367                                                 break;
00368                                         default:
00369                                                 /* If not recognized, display as is. */
00370                                                 break;
00371                                         }
00372                                         break;
00373 
00374                                 case 'CustomRendered':
00375                                         switch( $val ) {
00376                                         case 0: case 1:
00377                                                 $val = self::msg( $tag, $val );
00378                                                 break;
00379                                         default:
00380                                                 /* If not recognized, display as is. */
00381                                                 break;
00382                                         }
00383                                         break;
00384 
00385                                 case 'ExposureMode':
00386                                         switch( $val ) {
00387                                         case 0: case 1: case 2:
00388                                                 $val = self::msg( $tag, $val );
00389                                                 break;
00390                                         default:
00391                                                 /* If not recognized, display as is. */
00392                                                 break;
00393                                         }
00394                                         break;
00395 
00396                                 case 'WhiteBalance':
00397                                         switch( $val ) {
00398                                         case 0: case 1:
00399                                                 $val = self::msg( $tag, $val );
00400                                                 break;
00401                                         default:
00402                                                 /* If not recognized, display as is. */
00403                                                 break;
00404                                         }
00405                                         break;
00406 
00407                                 case 'SceneCaptureType':
00408                                         switch( $val ) {
00409                                         case 0: case 1: case 2: case 3:
00410                                                 $val = self::msg( $tag, $val );
00411                                                 break;
00412                                         default:
00413                                                 /* If not recognized, display as is. */
00414                                                 break;
00415                                         }
00416                                         break;
00417 
00418                                 case 'GainControl':
00419                                         switch( $val ) {
00420                                         case 0: case 1: case 2: case 3: case 4:
00421                                                 $val = self::msg( $tag, $val );
00422                                                 break;
00423                                         default:
00424                                                 /* If not recognized, display as is. */
00425                                                 break;
00426                                         }
00427                                         break;
00428 
00429                                 case 'Contrast':
00430                                         switch( $val ) {
00431                                         case 0: case 1: case 2:
00432                                                 $val = self::msg( $tag, $val );
00433                                                 break;
00434                                         default:
00435                                                 /* If not recognized, display as is. */
00436                                                 break;
00437                                         }
00438                                         break;
00439 
00440                                 case 'Saturation':
00441                                         switch( $val ) {
00442                                         case 0: case 1: case 2:
00443                                                 $val = self::msg( $tag, $val );
00444                                                 break;
00445                                         default:
00446                                                 /* If not recognized, display as is. */
00447                                                 break;
00448                                         }
00449                                         break;
00450 
00451                                 case 'Sharpness':
00452                                         switch( $val ) {
00453                                         case 0: case 1: case 2:
00454                                                 $val = self::msg( $tag, $val );
00455                                                 break;
00456                                         default:
00457                                                 /* If not recognized, display as is. */
00458                                                 break;
00459                                         }
00460                                         break;
00461 
00462                                 case 'SubjectDistanceRange':
00463                                         switch( $val ) {
00464                                         case 0: case 1: case 2: case 3:
00465                                                 $val = self::msg( $tag, $val );
00466                                                 break;
00467                                         default:
00468                                                 /* If not recognized, display as is. */
00469                                                 break;
00470                                         }
00471                                         break;
00472 
00473                                 //The GPS...Ref values are kept for compatibility, probably won't be reached.
00474                                 case 'GPSLatitudeRef':
00475                                 case 'GPSDestLatitudeRef':
00476                                         switch( $val ) {
00477                                         case 'N': case 'S':
00478                                                 $val = self::msg( 'GPSLatitude', $val );
00479                                                 break;
00480                                         default:
00481                                                 /* If not recognized, display as is. */
00482                                                 break;
00483                                         }
00484                                         break;
00485 
00486                                 case 'GPSLongitudeRef':
00487                                 case 'GPSDestLongitudeRef':
00488                                         switch( $val ) {
00489                                         case 'E': case 'W':
00490                                                 $val = self::msg( 'GPSLongitude', $val );
00491                                                 break;
00492                                         default:
00493                                                 /* If not recognized, display as is. */
00494                                                 break;
00495                                         }
00496                                         break;
00497 
00498                                 case 'GPSAltitude':
00499                                         if ( $val < 0 ) {
00500                                                 $val = self::msg( 'GPSAltitude', 'below-sealevel', self::formatNum( -$val, 3 ) );
00501                                         } else {
00502                                                 $val = self::msg( 'GPSAltitude', 'above-sealevel', self::formatNum( $val, 3 ) );
00503                                         }
00504                                         break;
00505 
00506                                 case 'GPSStatus':
00507                                         switch( $val ) {
00508                                         case 'A': case 'V':
00509                                                 $val = self::msg( $tag, $val );
00510                                                 break;
00511                                         default:
00512                                                 /* If not recognized, display as is. */
00513                                                 break;
00514                                         }
00515                                         break;
00516 
00517                                 case 'GPSMeasureMode':
00518                                         switch( $val ) {
00519                                         case 2: case 3:
00520                                                 $val = self::msg( $tag, $val );
00521                                                 break;
00522                                         default:
00523                                                 /* If not recognized, display as is. */
00524                                                 break;
00525                                         }
00526                                         break;
00527 
00528                                 case 'GPSTrackRef':
00529                                 case 'GPSImgDirectionRef':
00530                                 case 'GPSDestBearingRef':
00531                                         switch( $val ) {
00532                                         case 'T': case 'M':
00533                                                 $val = self::msg( 'GPSDirection', $val );
00534                                                 break;
00535                                         default:
00536                                                 /* If not recognized, display as is. */
00537                                                 break;
00538                                         }
00539                                         break;
00540 
00541                                 case 'GPSLatitude':
00542                                 case 'GPSDestLatitude':
00543                                         $val = self::formatCoords( $val, 'latitude' );
00544                                         break;
00545                                 case 'GPSLongitude':
00546                                 case 'GPSDestLongitude':
00547                                         $val = self::formatCoords( $val, 'longitude' );
00548                                         break;
00549 
00550                                 case 'GPSSpeedRef':
00551                                         switch( $val ) {
00552                                         case 'K': case 'M': case 'N':
00553                                                 $val = self::msg( 'GPSSpeed', $val );
00554                                                 break;
00555                                         default:
00556                                                 /* If not recognized, display as is. */
00557                                                 break;
00558                                         }
00559                                         break;
00560 
00561                                 case 'GPSDestDistanceRef':
00562                                         switch( $val ) {
00563                                         case 'K': case 'M': case 'N':
00564                                                 $val = self::msg( 'GPSDestDistance', $val );
00565                                                 break;
00566                                         default:
00567                                                 /* If not recognized, display as is. */
00568                                                 break;
00569                                         }
00570                                         break;
00571 
00572                                 case 'GPSDOP':
00573                                         // See http://en.wikipedia.org/wiki/Dilution_of_precision_(GPS)
00574                                         if ( $val <= 2 ) {
00575                                                 $val = self::msg( $tag, 'excellent', self::formatNum( $val ) );
00576                                         } elseif ( $val <= 5 ) {
00577                                                 $val = self::msg( $tag, 'good', self::formatNum( $val ) );
00578                                         } elseif ( $val <= 10 ) {
00579                                                 $val = self::msg( $tag, 'moderate', self::formatNum( $val ) );
00580                                         } elseif ( $val <= 20 ) {
00581                                                 $val = self::msg( $tag, 'fair', self::formatNum( $val ) );
00582                                         } else {
00583                                                 $val = self::msg( $tag, 'poor', self::formatNum( $val ) );
00584                                         }
00585                                         break;
00586 
00587                                 // This is not in the Exif standard, just a special
00588                                 // case for our purposes which enables wikis to wikify
00589                                 // the make, model and software name to link to their articles.
00590                                 case 'Make':
00591                                 case 'Model':
00592                                         $val = self::msg( $tag, '', $val );
00593                                         break;
00594 
00595                                 case 'Software':
00596                                         if ( is_array( $val ) ) {
00597                                                 //if its a software, version array.
00598                                                 $val = wfMessage( 'exif-software-version-value', $val[0], $val[1] )->text();
00599                                         } else {
00600                                                 $val = self::msg( $tag, '', $val );
00601                                         }
00602                                         break;
00603 
00604                                 case 'ExposureTime':
00605                                         // Show the pretty fraction as well as decimal version
00606                                         $val = wfMessage( 'exif-exposuretime-format',
00607                                                 self::formatFraction( $val ), self::formatNum( $val ) )->text();
00608                                         break;
00609                                 case 'ISOSpeedRatings':
00610                                         // If its = 65535 that means its at the
00611                                         // limit of the size of Exif::short and
00612                                         // is really higher.
00613                                         if ( $val == '65535' ) {
00614                                                 $val = self::msg( $tag, 'overflow' );
00615                                         } else {
00616                                                 $val = self::formatNum( $val );
00617                                         }
00618                                         break;
00619                                 case 'FNumber':
00620                                         $val = wfMessage( 'exif-fnumber-format',
00621                                                 self::formatNum( $val ) )->text();
00622                                         break;
00623 
00624                                 case 'FocalLength': case 'FocalLengthIn35mmFilm':
00625                                         $val = wfMessage( 'exif-focallength-format',
00626                                                 self::formatNum( $val ) )->text();
00627                                         break;
00628 
00629                                 case 'MaxApertureValue':
00630                                         if ( strpos( $val, '/' ) !== false ) {
00631                                                 // need to expand this earlier to calculate fNumber
00632                                                 list( $n, $d ) = explode( '/', $val );
00633                                                 if ( is_numeric( $n ) && is_numeric( $d ) ) {
00634                                                         $val = $n / $d;
00635                                                 }
00636                                         }
00637                                         if ( is_numeric( $val ) ) {
00638                                                 $fNumber = pow( 2, $val / 2 );
00639                                                 if ( $fNumber !== false ) {
00640                                                         $val = wfMessage( 'exif-maxaperturevalue-value',
00641                                                                 self::formatNum( $val ),
00642                                                                 self::formatNum( $fNumber, 2 )
00643                                                         )->text();
00644                                                 }
00645                                         }
00646                                         break;
00647 
00648                                 case 'iimCategory':
00649                                         switch( strtolower( $val ) ) {
00650                                                 // See pg 29 of IPTC photo
00651                                                 // metadata standard.
00652                                                 case 'ace': case 'clj':
00653                                                 case 'dis': case 'fin':
00654                                                 case 'edu': case 'evn':
00655                                                 case 'hth': case 'hum':
00656                                                 case 'lab': case 'lif':
00657                                                 case 'pol': case 'rel':
00658                                                 case 'sci': case 'soi':
00659                                                 case 'spo': case 'war':
00660                                                 case 'wea':
00661                                                         $val = self::msg(
00662                                                                 'iimcategory',
00663                                                                 $val
00664                                                         );
00665                                         }
00666                                         break;
00667                                 case 'SubjectNewsCode':
00668                                         // Essentially like iimCategory.
00669                                         // 8 (numeric) digit hierarchical
00670                                         // classification. We decode the
00671                                         // first 2 digits, which provide
00672                                         // a broad category.
00673                                         $val = self::convertNewsCode( $val );
00674                                         break;
00675                                 case 'Urgency':
00676                                         // 1-8 with 1 being highest, 5 normal
00677                                         // 0 is reserved, and 9 is 'user-defined'.
00678                                         $urgency = '';
00679                                         if ( $val == 0 || $val == 9 ) {
00680                                                 $urgency = 'other';
00681                                         } elseif ( $val < 5 && $val > 1 ) {
00682                                                 $urgency = 'high';
00683                                         } elseif ( $val == 5 ) {
00684                                                 $urgency = 'normal';
00685                                         } elseif ( $val <= 8 && $val > 5) {
00686                                                 $urgency = 'low';
00687                                         }
00688 
00689                                         if ( $urgency !== '' ) {
00690                                                 $val = self::msg( 'urgency',
00691                                                         $urgency, $val
00692                                                 );
00693                                         }
00694                                         break;
00695 
00696                                 // Things that have a unit of pixels.
00697                                 case 'OriginalImageHeight':
00698                                 case 'OriginalImageWidth':
00699                                 case 'PixelXDimension':
00700                                 case 'PixelYDimension':
00701                                 case 'ImageWidth':
00702                                 case 'ImageLength':
00703                                         $val = self::formatNum( $val ) . ' ' . wfMessage( 'unit-pixel' )->text();
00704                                         break;
00705 
00706                                 // Do not transform fields with pure text.
00707                                 // For some languages the formatNum()
00708                                 // conversion results to wrong output like
00709                                 // foo,bar@example,com or fooÙ«bar@exampleÙ«com.
00710                                 // Also some 'numeric' things like Scene codes
00711                                 // are included here as we really don't want
00712                                 // commas inserted.
00713                                 case 'ImageDescription':
00714                                 case 'Artist':
00715                                 case 'Copyright':
00716                                 case 'RelatedSoundFile':
00717                                 case 'ImageUniqueID':
00718                                 case 'SpectralSensitivity':
00719                                 case 'GPSSatellites':
00720                                 case 'GPSVersionID':
00721                                 case 'GPSMapDatum':
00722                                 case 'Keywords':
00723                                 case 'WorldRegionDest':
00724                                 case 'CountryDest':
00725                                 case 'CountryCodeDest':
00726                                 case 'ProvinceOrStateDest':
00727                                 case 'CityDest':
00728                                 case 'SublocationDest':
00729                                 case 'WorldRegionCreated':
00730                                 case 'CountryCreated':
00731                                 case 'CountryCodeCreated':
00732                                 case 'ProvinceOrStateCreated':
00733                                 case 'CityCreated':
00734                                 case 'SublocationCreated':
00735                                 case 'ObjectName':
00736                                 case 'SpecialInstructions':
00737                                 case 'Headline':
00738                                 case 'Credit':
00739                                 case 'Source':
00740                                 case 'EditStatus':
00741                                 case 'FixtureIdentifier':
00742                                 case 'LocationDest':
00743                                 case 'LocationDestCode':
00744                                 case 'Writer':
00745                                 case 'JPEGFileComment':
00746                                 case 'iimSupplementalCategory':
00747                                 case 'OriginalTransmissionRef':
00748                                 case 'Identifier':
00749                                 case 'dc-contributor':
00750                                 case 'dc-coverage':
00751                                 case 'dc-publisher':
00752                                 case 'dc-relation':
00753                                 case 'dc-rights':
00754                                 case 'dc-source':
00755                                 case 'dc-type':
00756                                 case 'Lens':
00757                                 case 'SerialNumber':
00758                                 case 'CameraOwnerName':
00759                                 case 'Label':
00760                                 case 'Nickname':
00761                                 case 'RightsCertificate':
00762                                 case 'CopyrightOwner':
00763                                 case 'UsageTerms':
00764                                 case 'WebStatement':
00765                                 case 'OriginalDocumentID':
00766                                 case 'LicenseUrl':
00767                                 case 'MorePermissionsUrl':
00768                                 case 'AttributionUrl':
00769                                 case 'PreferredAttributionName':
00770                                 case 'PNGFileComment':
00771                                 case 'Disclaimer':
00772                                 case 'ContentWarning':
00773                                 case 'GIFFileComment':
00774                                 case 'SceneCode':
00775                                 case 'IntellectualGenre':
00776                                 case 'Event':
00777                                 case 'OrginisationInImage':
00778                                 case 'PersonInImage':
00779 
00780                                         $val = htmlspecialchars( $val );
00781                                         break;
00782 
00783                                 case 'ObjectCycle':
00784                                         switch ( $val ) {
00785                                         case 'a': case 'p': case 'b':
00786                                                 $val = self::msg( $tag, $val );
00787                                                 break;
00788                                         default:
00789                                                 $val = htmlspecialchars( $val );
00790                                                 break;
00791                                         }
00792                                         break;
00793                                 case 'Copyrighted':
00794                                         switch( $val ) {
00795                                         case 'True': case 'False':
00796                                                 $val = self::msg( $tag, $val );
00797                                                 break;
00798                                         }
00799                                         break;
00800                                 case 'Rating':
00801                                         if ( $val == '-1' ) {
00802                                                 $val = self::msg( $tag, 'rejected' );
00803                                         } else {
00804                                                 $val = self::formatNum( $val );
00805                                         }
00806                                         break;
00807 
00808                                 case 'LanguageCode':
00809                                         $lang = Language::fetchLanguageName( strtolower( $val ), $wgLang->getCode() );
00810                                         if ( $lang ) {
00811                                                 $val = htmlspecialchars( $lang );
00812                                         } else {
00813                                                 $val = htmlspecialchars( $val );
00814                                         }
00815                                         break;
00816 
00817                                 default:
00818                                         $val = self::formatNum( $val );
00819                                         break;
00820                                 }
00821                         }
00822                         // End formatting values, start flattening arrays.
00823                         $vals = self::flattenArray( $vals, $type );
00824 
00825                 }
00826                 return $tags;
00827         }
00828 
00844         public static function flattenArray( $vals, $type = 'ul', $noHtml = false ) {
00845                 if ( isset( $vals['_type'] ) ) {
00846                         $type = $vals['_type'];
00847                         unset( $vals['_type'] );
00848                 }
00849 
00850                 if ( !is_array( $vals ) ) {
00851                         return $vals; // do nothing if not an array;
00852                 }
00853                 elseif ( count( $vals ) === 1 && $type !== 'lang' ) {
00854                         return $vals[0];
00855                 }
00856                 elseif ( count( $vals ) === 0 ) {
00857                         wfDebug( __METHOD__ . ' metadata array with 0 elements!' );
00858                         return ""; // paranoia. This should never happen
00859                 }
00860                 /* @todo FIXME: This should hide some of the list entries if there are
00861                  * say more than four. Especially if a field is translated into 20
00862                  * languages, we don't want to show them all by default
00863                  */
00864                 else {
00865                         global $wgContLang;
00866                         switch( $type ) {
00867                         case 'lang':
00868                                 // Display default, followed by ContLang,
00869                                 // followed by the rest in no particular
00870                                 // order.
00871 
00872                                 // Todo: hide some items if really long list.
00873 
00874                                 $content = '';
00875 
00876                                 $cLang = $wgContLang->getCode();
00877                                 $defaultItem = false;
00878                                 $defaultLang = false;
00879 
00880                                 // If default is set, save it for later,
00881                                 // as we don't know if it's equal to
00882                                 // one of the lang codes. (In xmp
00883                                 // you specify the language for a
00884                                 // default property by having both
00885                                 // a default prop, and one in the language
00886                                 // that are identical)
00887                                 if ( isset( $vals['x-default'] ) ) {
00888                                         $defaultItem = $vals['x-default'];
00889                                         unset( $vals['x-default'] );
00890                                 }
00891                                 // Do contentLanguage.
00892                                 if ( isset( $vals[$cLang] ) ) {
00893                                         $isDefault = false;
00894                                         if ( $vals[$cLang] === $defaultItem ) {
00895                                                 $defaultItem = false;
00896                                                 $isDefault = true;
00897                                         }
00898                                         $content .= self::langItem(
00899                                                 $vals[$cLang], $cLang,
00900                                                 $isDefault, $noHtml );
00901 
00902                                         unset( $vals[$cLang] );
00903                                 }
00904 
00905                                 // Now do the rest.
00906                                 foreach ( $vals as $lang => $item ) {
00907                                         if ( $item === $defaultItem ) {
00908                                                 $defaultLang = $lang;
00909                                                 continue;
00910                                         }
00911                                         $content .= self::langItem( $item,
00912                                                 $lang, false, $noHtml );
00913                                 }
00914                                 if ( $defaultItem !== false ) {
00915                                         $content = self::langItem( $defaultItem,
00916                                                 $defaultLang, true, $noHtml ) .
00917                                                 $content;
00918                                 }
00919                                 if ( $noHtml ) {
00920                                         return $content;
00921                                 }
00922                                 return '<ul class="metadata-langlist">' .
00923                                         $content .
00924                                         '</ul>';
00925                         case 'ol':
00926                                 if ( $noHtml ) {
00927                                         return "\n#" . implode( "\n#", $vals );
00928                                 }
00929                                 return "<ol><li>" . implode( "</li>\n<li>", $vals ) . '</li></ol>';
00930                         case 'ul':
00931                         default:
00932                                 if ( $noHtml ) {
00933                                         return "\n*" . implode( "\n*", $vals );
00934                                 }
00935                                 return "<ul><li>" . implode( "</li>\n<li>", $vals ) . '</li></ul>';
00936                         }
00937                 }
00938         }
00939 
00950         private static function langItem( $value, $lang, $default = false, $noHtml = false ) {
00951                 if ( $lang === false && $default === false) {
00952                         throw new MWException( '$lang and $default cannot both '
00953                                 . 'be false.' );
00954                 }
00955 
00956                 if ( $noHtml ) {
00957                         $wrappedValue = $value;
00958                 } else {
00959                         $wrappedValue = '<span class="mw-metadata-lang-value">'
00960                                 . $value . '</span>';
00961                 }
00962 
00963                 if ( $lang === false ) {
00964                         if ( $noHtml ) {
00965                                 return wfMessage( 'metadata-langitem-default',
00966                                         $wrappedValue )->text() . "\n\n";
00967                         } /* else */
00968                         return '<li class="mw-metadata-lang-default">'
00969                                 . wfMessage( 'metadata-langitem-default',
00970                                         $wrappedValue )->text()
00971                                 . "</li>\n";
00972                 }
00973 
00974                 $lowLang = strtolower( $lang );
00975                 $langName = Language::fetchLanguageName( $lowLang );
00976                 if ( $langName === '' ) {
00977                         //try just the base language name. (aka en-US -> en ).
00978                         list( $langPrefix ) = explode( '-', $lowLang, 2 );
00979                         $langName = Language::fetchLanguageName( $langPrefix );
00980                         if ( $langName === '' ) {
00981                                 // give up.
00982                                 $langName = $lang;
00983                         }
00984                 }
00985                 // else we have a language specified
00986 
00987                 if ( $noHtml ) {
00988                         return '*' . wfMessage( 'metadata-langitem',
00989                                 $wrappedValue, $langName, $lang )->text();
00990                 } /* else: */
00991 
00992                 $item = '<li class="mw-metadata-lang-code-'
00993                         . $lang;
00994                 if ( $default ) {
00995                         $item .= ' mw-metadata-lang-default';
00996                 }
00997                 $item .= '" lang="' . $lang . '">';
00998                 $item .= wfMessage( 'metadata-langitem',
00999                         $wrappedValue, $langName, $lang )->text();
01000                 $item .= "</li>\n";
01001                 return $item;
01002         }
01003 
01015         static function msg( $tag, $val, $arg = null, $arg2 = null ) {
01016                 global $wgContLang;
01017 
01018                 if ( $val === '' )
01019                         $val = 'value';
01020                 return wfMessage( $wgContLang->lc( "exif-$tag-$val" ), $arg, $arg2 )->text();
01021         }
01022 
01031         static function formatNum( $num, $round = false ) {
01032                 global $wgLang;
01033                 $m = array();
01034                 if( is_array( $num ) ) {
01035                         $out = array();
01036                         foreach( $num as $number ) {
01037                                 $out[] = self::formatNum( $number );
01038                         }
01039                         return $wgLang->commaList( $out );
01040                 }
01041                 if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
01042                         if ( $m[2] != 0 ) {
01043                                 $newNum = $m[1] / $m[2];
01044                                 if ( $round !== false ) {
01045                                         $newNum = round( $newNum, $round );
01046                                 }
01047                         } else {
01048                                 $newNum = $num;
01049                         }
01050 
01051                         return $wgLang->formatNum( $newNum );
01052                 } else {
01053                         if ( is_numeric( $num ) && $round !== false ) {
01054                                 $num = round( $num, $round );
01055                         }
01056                         return $wgLang->formatNum( $num );
01057                 }
01058         }
01059 
01068         static function formatFraction( $num ) {
01069                 $m = array();
01070                 if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
01071                         $numerator = intval( $m[1] );
01072                         $denominator = intval( $m[2] );
01073                         $gcd = self::gcd( abs( $numerator ), $denominator );
01074                         if( $gcd != 0 ) {
01075                                 // 0 shouldn't happen! ;)
01076                                 return self::formatNum( $numerator / $gcd ) . '/' . self::formatNum( $denominator / $gcd );
01077                         }
01078                 }
01079                 return self::formatNum( $num );
01080         }
01081 
01090         static function gcd( $a, $b ) {
01091                 /*
01092                         // http://en.wikipedia.org/wiki/Euclidean_algorithm
01093                         // Recursive form would be:
01094                         if( $b == 0 )
01095                                 return $a;
01096                         else
01097                                 return gcd( $b, $a % $b );
01098                 */
01099                 while( $b != 0 ) {
01100                         $remainder = $a % $b;
01101 
01102                         // tail recursion...
01103                         $a = $b;
01104                         $b = $remainder;
01105                 }
01106                 return $a;
01107         }
01108 
01121         private static function convertNewsCode( $val ) {
01122                 if ( !preg_match( '/^\d{8}$/D', $val ) ) {
01123                         // Not a valid news code.
01124                         return $val;
01125                 }
01126                 $cat = '';
01127                 switch( substr( $val, 0, 2 ) ) {
01128                         case '01':
01129                                 $cat = 'ace';
01130                                 break;
01131                         case '02':
01132                                 $cat = 'clj';
01133                                 break;
01134                         case '03':
01135                                 $cat = 'dis';
01136                                 break;
01137                         case '04':
01138                                 $cat = 'fin';
01139                                 break;
01140                         case '05':
01141                                 $cat = 'edu';
01142                                 break;
01143                         case '06':
01144                                 $cat = 'evn';
01145                                 break;
01146                         case '07':
01147                                 $cat = 'hth';
01148                                 break;
01149                         case '08':
01150                                 $cat = 'hum';
01151                                 break;
01152                         case '09':
01153                                 $cat = 'lab';
01154                                 break;
01155                         case '10':
01156                                 $cat = 'lif';
01157                                 break;
01158                         case '11':
01159                                 $cat = 'pol';
01160                                 break;
01161                         case '12':
01162                                 $cat = 'rel';
01163                                 break;
01164                         case '13':
01165                                 $cat = 'sci';
01166                                 break;
01167                         case '14':
01168                                 $cat = 'soi';
01169                                 break;
01170                         case '15':
01171                                 $cat = 'spo';
01172                                 break;
01173                         case '16':
01174                                 $cat = 'war';
01175                                 break;
01176                         case '17':
01177                                 $cat = 'wea';
01178                                 break;
01179                 }
01180                 if ( $cat !== '' ) {
01181                         $catMsg = self::msg( 'iimcategory', $cat );
01182                         $val = self::msg( 'subjectnewscode', '', $val, $catMsg );
01183                 }
01184                 return $val;
01185         }
01186 
01195         static function formatCoords( $coord, $type ) {
01196                 $ref = '';
01197                 if ( $coord < 0 ) {
01198                         $nCoord = -$coord;
01199                         if ( $type === 'latitude' ) {
01200                                 $ref = 'S';
01201                         } elseif ( $type === 'longitude' ) {
01202                                 $ref = 'W';
01203                         }
01204                 } else {
01205                         $nCoord = $coord;
01206                         if ( $type === 'latitude' ) {
01207                                 $ref = 'N';
01208                         } elseif ( $type === 'longitude' ) {
01209                                 $ref = 'E';
01210                         }
01211                 }
01212 
01213                 $deg = floor( $nCoord );
01214                 $min = floor( ( $nCoord - $deg ) * 60.0 );
01215                 $sec = round( ( ( $nCoord - $deg ) - $min / 60 ) * 3600, 2 );
01216 
01217                 $deg = self::formatNum( $deg );
01218                 $min = self::formatNum( $min );
01219                 $sec = self::formatNum( $sec );
01220 
01221                 return wfMessage( 'exif-coordinate-format', $deg, $min, $sec, $ref, $coord )->text();
01222         }
01223 
01238         public static function collapseContactInfo( $vals ) {
01239                 if( !( isset( $vals['CiAdrExtadr'] )
01240                         || isset( $vals['CiAdrCity'] )
01241                         || isset( $vals['CiAdrCtry'] )
01242                         || isset( $vals['CiEmailWork'] )
01243                         || isset( $vals['CiTelWork'] )
01244                         || isset( $vals['CiAdrPcode'] )
01245                         || isset( $vals['CiAdrRegion'] )
01246                         || isset( $vals['CiUrlWork'] )
01247                 ) ) {
01248                         // We don't have any sub-properties
01249                         // This could happen if its using old
01250                         // iptc that just had this as a free-form
01251                         // text value.
01252                         // Note: We run this through htmlspecialchars
01253                         // partially to be consistent, and partially
01254                         // because people often insert >, etc into
01255                         // the metadata which should not be interpreted
01256                         // but we still want to auto-link urls.
01257                         foreach( $vals as &$val ) {
01258                                 $val = htmlspecialchars( $val );
01259                         }
01260                         return self::flattenArray( $vals );
01261                 } else {
01262                         // We have a real ContactInfo field.
01263                         // Its unclear if all these fields have to be
01264                         // set, so assume they do not.
01265                         $url = $tel = $street = $city = $country = '';
01266                         $email = $postal = $region = '';
01267 
01268                         // Also note, some of the class names this uses
01269                         // are similar to those used by hCard. This is
01270                         // mostly because they're sensible names. This
01271                         // does not (and does not attempt to) output
01272                         // stuff in the hCard microformat. However it
01273                         // might output in the adr microformat.
01274 
01275                         if ( isset( $vals['CiAdrExtadr'] ) ) {
01276                                 // Todo: This can potentially be multi-line.
01277                                 // Need to check how that works in XMP.
01278                                 $street = '<span class="extended-address">'
01279                                         . htmlspecialchars(
01280                                                 $vals['CiAdrExtadr'] )
01281                                         . '</span>';
01282                         }
01283                         if ( isset( $vals['CiAdrCity'] ) ) {
01284                                 $city = '<span class="locality">'
01285                                         . htmlspecialchars( $vals['CiAdrCity'] )
01286                                         . '</span>';
01287                         }
01288                         if ( isset( $vals['CiAdrCtry'] ) ) {
01289                                 $country = '<span class="country-name">'
01290                                         . htmlspecialchars( $vals['CiAdrCtry'] )
01291                                         . '</span>';
01292                         }
01293                         if ( isset( $vals['CiEmailWork'] ) ) {
01294                                 $emails = array();
01295                                 // Have to split multiple emails at commas/new lines.
01296                                 $splitEmails = explode( "\n", $vals['CiEmailWork'] );
01297                                 foreach ( $splitEmails as $e1 ) {
01298                                         // Also split on comma
01299                                         foreach ( explode( ',', $e1 ) as $e2 ) {
01300                                                 $finalEmail = trim( $e2 );
01301                                                 if ( $finalEmail == ',' || $finalEmail == '' ) {
01302                                                         continue;
01303                                                 }
01304                                                 if ( strpos( $finalEmail, '<' ) !== false ) {
01305                                                         // Don't do fancy formatting to
01306                                                         // "My name" <[email protected]> style stuff
01307                                                         $emails[] = $finalEmail;
01308                                                 } else {
01309                                                         $emails[] = '[mailto:'
01310                                                         . $finalEmail
01311                                                         . ' <span class="email">'
01312                                                         . $finalEmail
01313                                                         . '</span>]';
01314                                                 }
01315                                         }
01316                                 }
01317                                 $email = implode( ', ', $emails );
01318                         }
01319                         if ( isset( $vals['CiTelWork'] ) ) {
01320                                 $tel = '<span class="tel">'
01321                                         . htmlspecialchars( $vals['CiTelWork'] )
01322                                         . '</span>';
01323                         }
01324                         if ( isset( $vals['CiAdrPcode'] ) ) {
01325                                 $postal = '<span class="postal-code">'
01326                                         . htmlspecialchars(
01327                                                 $vals['CiAdrPcode'] )
01328                                         . '</span>';
01329                         }
01330                         if ( isset( $vals['CiAdrRegion'] ) ) {
01331                                 // Note this is province/state.
01332                                 $region = '<span class="region">'
01333                                         . htmlspecialchars(
01334                                                 $vals['CiAdrRegion'] )
01335                                         . '</span>';
01336                         }
01337                         if ( isset( $vals['CiUrlWork'] ) ) {
01338                                 $url = '<span class="url">'
01339                                         . htmlspecialchars( $vals['CiUrlWork'] )
01340                                         . '</span>';
01341                         }
01342                         return wfMessage( 'exif-contact-value', $email, $url,
01343                                 $street, $city, $region, $postal, $country,
01344                                 $tel )->text();
01345                 }
01346         }
01347 }
01348 
01355 class FormatExif {
01356         var $meta;
01357 
01361         function FormatExif( $meta ) {
01362                 wfDeprecated( __METHOD__, '1.18' );
01363                 $this->meta = $meta;
01364         }
01365 
01369         function getFormattedData() {
01370                 return FormatMetadata::getFormattedData( $this->meta );
01371         }
01372 }