MediaWiki
REL1_22
|
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 }