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