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