MediaWiki  REL1_22
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!\n" );
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         }
01021         return wfMessage( $wgContLang->lc( "exif-$tag-$val" ), $arg, $arg2 )->text();
01022     }
01023 
01032     static function formatNum( $num, $round = false ) {
01033         global $wgLang;
01034         $m = array();
01035         if ( is_array( $num ) ) {
01036             $out = array();
01037             foreach ( $num as $number ) {
01038                 $out[] = self::formatNum( $number );
01039             }
01040             return $wgLang->commaList( $out );
01041         }
01042         if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
01043             if ( $m[2] != 0 ) {
01044                 $newNum = $m[1] / $m[2];
01045                 if ( $round !== false ) {
01046                     $newNum = round( $newNum, $round );
01047                 }
01048             } else {
01049                 $newNum = $num;
01050             }
01051 
01052             return $wgLang->formatNum( $newNum );
01053         } else {
01054             if ( is_numeric( $num ) && $round !== false ) {
01055                 $num = round( $num, $round );
01056             }
01057             return $wgLang->formatNum( $num );
01058         }
01059     }
01060 
01069     static function formatFraction( $num ) {
01070         $m = array();
01071         if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
01072             $numerator = intval( $m[1] );
01073             $denominator = intval( $m[2] );
01074             $gcd = self::gcd( abs( $numerator ), $denominator );
01075             if ( $gcd != 0 ) {
01076                 // 0 shouldn't happen! ;)
01077                 return self::formatNum( $numerator / $gcd ) . '/' . self::formatNum( $denominator / $gcd );
01078             }
01079         }
01080         return self::formatNum( $num );
01081     }
01082 
01091     static function gcd( $a, $b ) {
01092         /*
01093             // http://en.wikipedia.org/wiki/Euclidean_algorithm
01094             // Recursive form would be:
01095             if( $b == 0 )
01096                 return $a;
01097             else
01098                 return gcd( $b, $a % $b );
01099         */
01100         while ( $b != 0 ) {
01101             $remainder = $a % $b;
01102 
01103             // tail recursion...
01104             $a = $b;
01105             $b = $remainder;
01106         }
01107         return $a;
01108     }
01109 
01122     private static function convertNewsCode( $val ) {
01123         if ( !preg_match( '/^\d{8}$/D', $val ) ) {
01124             // Not a valid news code.
01125             return $val;
01126         }
01127         $cat = '';
01128         switch ( substr( $val, 0, 2 ) ) {
01129             case '01':
01130                 $cat = 'ace';
01131                 break;
01132             case '02':
01133                 $cat = 'clj';
01134                 break;
01135             case '03':
01136                 $cat = 'dis';
01137                 break;
01138             case '04':
01139                 $cat = 'fin';
01140                 break;
01141             case '05':
01142                 $cat = 'edu';
01143                 break;
01144             case '06':
01145                 $cat = 'evn';
01146                 break;
01147             case '07':
01148                 $cat = 'hth';
01149                 break;
01150             case '08':
01151                 $cat = 'hum';
01152                 break;
01153             case '09':
01154                 $cat = 'lab';
01155                 break;
01156             case '10':
01157                 $cat = 'lif';
01158                 break;
01159             case '11':
01160                 $cat = 'pol';
01161                 break;
01162             case '12':
01163                 $cat = 'rel';
01164                 break;
01165             case '13':
01166                 $cat = 'sci';
01167                 break;
01168             case '14':
01169                 $cat = 'soi';
01170                 break;
01171             case '15':
01172                 $cat = 'spo';
01173                 break;
01174             case '16':
01175                 $cat = 'war';
01176                 break;
01177             case '17':
01178                 $cat = 'wea';
01179                 break;
01180         }
01181         if ( $cat !== '' ) {
01182             $catMsg = self::msg( 'iimcategory', $cat );
01183             $val = self::msg( 'subjectnewscode', '', $val, $catMsg );
01184         }
01185         return $val;
01186     }
01187 
01196     static function formatCoords( $coord, $type ) {
01197         $ref = '';
01198         if ( $coord < 0 ) {
01199             $nCoord = -$coord;
01200             if ( $type === 'latitude' ) {
01201                 $ref = 'S';
01202             } elseif ( $type === 'longitude' ) {
01203                 $ref = 'W';
01204             }
01205         } else {
01206             $nCoord = $coord;
01207             if ( $type === 'latitude' ) {
01208                 $ref = 'N';
01209             } elseif ( $type === 'longitude' ) {
01210                 $ref = 'E';
01211             }
01212         }
01213 
01214         $deg = floor( $nCoord );
01215         $min = floor( ( $nCoord - $deg ) * 60.0 );
01216         $sec = round( ( ( $nCoord - $deg ) - $min / 60 ) * 3600, 2 );
01217 
01218         $deg = self::formatNum( $deg );
01219         $min = self::formatNum( $min );
01220         $sec = self::formatNum( $sec );
01221 
01222         return wfMessage( 'exif-coordinate-format', $deg, $min, $sec, $ref, $coord )->text();
01223     }
01224 
01239     public static function collapseContactInfo( $vals ) {
01240         if ( !( isset( $vals['CiAdrExtadr'] )
01241             || isset( $vals['CiAdrCity'] )
01242             || isset( $vals['CiAdrCtry'] )
01243             || isset( $vals['CiEmailWork'] )
01244             || isset( $vals['CiTelWork'] )
01245             || isset( $vals['CiAdrPcode'] )
01246             || isset( $vals['CiAdrRegion'] )
01247             || isset( $vals['CiUrlWork'] )
01248         ) ) {
01249             // We don't have any sub-properties
01250             // This could happen if its using old
01251             // iptc that just had this as a free-form
01252             // text value.
01253             // Note: We run this through htmlspecialchars
01254             // partially to be consistent, and partially
01255             // because people often insert >, etc into
01256             // the metadata which should not be interpreted
01257             // but we still want to auto-link urls.
01258             foreach ( $vals as &$val ) {
01259                 $val = htmlspecialchars( $val );
01260             }
01261             return self::flattenArray( $vals );
01262         } else {
01263             // We have a real ContactInfo field.
01264             // Its unclear if all these fields have to be
01265             // set, so assume they do not.
01266             $url = $tel = $street = $city = $country = '';
01267             $email = $postal = $region = '';
01268 
01269             // Also note, some of the class names this uses
01270             // are similar to those used by hCard. This is
01271             // mostly because they're sensible names. This
01272             // does not (and does not attempt to) output
01273             // stuff in the hCard microformat. However it
01274             // might output in the adr microformat.
01275 
01276             if ( isset( $vals['CiAdrExtadr'] ) ) {
01277                 // Todo: This can potentially be multi-line.
01278                 // Need to check how that works in XMP.
01279                 $street = '<span class="extended-address">'
01280                     . htmlspecialchars(
01281                         $vals['CiAdrExtadr'] )
01282                     . '</span>';
01283             }
01284             if ( isset( $vals['CiAdrCity'] ) ) {
01285                 $city = '<span class="locality">'
01286                     . htmlspecialchars( $vals['CiAdrCity'] )
01287                     . '</span>';
01288             }
01289             if ( isset( $vals['CiAdrCtry'] ) ) {
01290                 $country = '<span class="country-name">'
01291                     . htmlspecialchars( $vals['CiAdrCtry'] )
01292                     . '</span>';
01293             }
01294             if ( isset( $vals['CiEmailWork'] ) ) {
01295                 $emails = array();
01296                 // Have to split multiple emails at commas/new lines.
01297                 $splitEmails = explode( "\n", $vals['CiEmailWork'] );
01298                 foreach ( $splitEmails as $e1 ) {
01299                     // Also split on comma
01300                     foreach ( explode( ',', $e1 ) as $e2 ) {
01301                         $finalEmail = trim( $e2 );
01302                         if ( $finalEmail == ',' || $finalEmail == '' ) {
01303                             continue;
01304                         }
01305                         if ( strpos( $finalEmail, '<' ) !== false ) {
01306                             // Don't do fancy formatting to
01307                             // "My name" <[email protected]> style stuff
01308                             $emails[] = $finalEmail;
01309                         } else {
01310                             $emails[] = '[mailto:'
01311                             . $finalEmail
01312                             . ' <span class="email">'
01313                             . $finalEmail
01314                             . '</span>]';
01315                         }
01316                     }
01317                 }
01318                 $email = implode( ', ', $emails );
01319             }
01320             if ( isset( $vals['CiTelWork'] ) ) {
01321                 $tel = '<span class="tel">'
01322                     . htmlspecialchars( $vals['CiTelWork'] )
01323                     . '</span>';
01324             }
01325             if ( isset( $vals['CiAdrPcode'] ) ) {
01326                 $postal = '<span class="postal-code">'
01327                     . htmlspecialchars(
01328                         $vals['CiAdrPcode'] )
01329                     . '</span>';
01330             }
01331             if ( isset( $vals['CiAdrRegion'] ) ) {
01332                 // Note this is province/state.
01333                 $region = '<span class="region">'
01334                     . htmlspecialchars(
01335                         $vals['CiAdrRegion'] )
01336                     . '</span>';
01337             }
01338             if ( isset( $vals['CiUrlWork'] ) ) {
01339                 $url = '<span class="url">'
01340                     . htmlspecialchars( $vals['CiUrlWork'] )
01341                     . '</span>';
01342             }
01343             return wfMessage( 'exif-contact-value', $email, $url,
01344                 $street, $city, $region, $postal, $country,
01345                 $tel )->text();
01346         }
01347     }
01348 }
01349 
01356 class FormatExif {
01357     var $meta;
01358 
01362     function FormatExif( $meta ) {
01363         wfDeprecated( __METHOD__, '1.18' );
01364         $this->meta = $meta;
01365     }
01366 
01370     function getFormattedData() {
01371         return FormatMetadata::getFormattedData( $this->meta );
01372     }
01373 }