MediaWiki
REL1_23
|
00001 <?php 00049 class FormatMetadata extends ContextSource { 00055 protected $singleLang = false; 00056 00063 public function setSingleLanguage( $val ) { 00064 $this->singleLang = $val; 00065 } 00066 00080 public static function getFormattedData( $tags, $context = false ) { 00081 $obj = new FormatMetadata; 00082 if ( $context ) { 00083 $obj->setContext( $context ); 00084 } 00085 00086 return $obj->makeFormattedData( $tags ); 00087 } 00088 00100 public function makeFormattedData( $tags ) { 00101 $resolutionunit = !isset( $tags['ResolutionUnit'] ) || $tags['ResolutionUnit'] == 2 ? 2 : 3; 00102 unset( $tags['ResolutionUnit'] ); 00103 00104 foreach ( $tags as $tag => &$vals ) { 00105 00106 // This seems ugly to wrap non-array's in an array just to unwrap again, 00107 // especially when most of the time it is not an array 00108 if ( !is_array( $tags[$tag] ) ) { 00109 $vals = array( $vals ); 00110 } 00111 00112 // _type is a special value to say what array type 00113 if ( isset( $tags[$tag]['_type'] ) ) { 00114 $type = $tags[$tag]['_type']; 00115 unset( $vals['_type'] ); 00116 } else { 00117 $type = 'ul'; // default unordered list. 00118 } 00119 00120 //This is done differently as the tag is an array. 00121 if ( $tag == 'GPSTimeStamp' && count( $vals ) === 3 ) { 00122 //hour min sec array 00123 00124 $h = explode( '/', $vals[0] ); 00125 $m = explode( '/', $vals[1] ); 00126 $s = explode( '/', $vals[2] ); 00127 00128 // this should already be validated 00129 // when loaded from file, but it could 00130 // come from a foreign repo, so be 00131 // paranoid. 00132 if ( !isset( $h[1] ) 00133 || !isset( $m[1] ) 00134 || !isset( $s[1] ) 00135 || $h[1] == 0 00136 || $m[1] == 0 00137 || $s[1] == 0 00138 ) { 00139 continue; 00140 } 00141 $tags[$tag] = str_pad( intval( $h[0] / $h[1] ), 2, '0', STR_PAD_LEFT ) 00142 . ':' . str_pad( intval( $m[0] / $m[1] ), 2, '0', STR_PAD_LEFT ) 00143 . ':' . str_pad( intval( $s[0] / $s[1] ), 2, '0', STR_PAD_LEFT ); 00144 00145 try { 00146 $time = wfTimestamp( TS_MW, '1971:01:01 ' . $tags[$tag] ); 00147 // the 1971:01:01 is just a placeholder, and not shown to user. 00148 if ( $time && intval( $time ) > 0 ) { 00149 $tags[$tag] = $this->getLanguage()->time( $time ); 00150 } 00151 } catch ( TimestampException $e ) { 00152 // This shouldn't happen, but we've seen bad formats 00153 // such as 4-digit seconds in the wild. 00154 // leave $tags[$tag] as-is 00155 } 00156 continue; 00157 } 00158 00159 // The contact info is a multi-valued field 00160 // instead of the other props which are single 00161 // valued (mostly) so handle as a special case. 00162 if ( $tag === 'Contact' ) { 00163 $vals = $this->collapseContactInfo( $vals ); 00164 continue; 00165 } 00166 00167 foreach ( $vals as &$val ) { 00168 00169 switch ( $tag ) { 00170 case 'Compression': 00171 switch ( $val ) { 00172 case 1: 00173 case 2: 00174 case 3: 00175 case 4: 00176 case 5: 00177 case 6: 00178 case 7: 00179 case 8: 00180 case 32773: 00181 case 32946: 00182 case 34712: 00183 $val = $this->exifMsg( $tag, $val ); 00184 break; 00185 default: 00186 /* If not recognized, display as is. */ 00187 break; 00188 } 00189 break; 00190 00191 case 'PhotometricInterpretation': 00192 switch ( $val ) { 00193 case 2: 00194 case 6: 00195 $val = $this->exifMsg( $tag, $val ); 00196 break; 00197 default: 00198 /* If not recognized, display as is. */ 00199 break; 00200 } 00201 break; 00202 00203 case 'Orientation': 00204 switch ( $val ) { 00205 case 1: 00206 case 2: 00207 case 3: 00208 case 4: 00209 case 5: 00210 case 6: 00211 case 7: 00212 case 8: 00213 $val = $this->exifMsg( $tag, $val ); 00214 break; 00215 default: 00216 /* If not recognized, display as is. */ 00217 break; 00218 } 00219 break; 00220 00221 case 'PlanarConfiguration': 00222 switch ( $val ) { 00223 case 1: 00224 case 2: 00225 $val = $this->exifMsg( $tag, $val ); 00226 break; 00227 default: 00228 /* If not recognized, display as is. */ 00229 break; 00230 } 00231 break; 00232 00233 // TODO: YCbCrSubSampling 00234 case 'YCbCrPositioning': 00235 switch ( $val ) { 00236 case 1: 00237 case 2: 00238 $val = $this->exifMsg( $tag, $val ); 00239 break; 00240 default: 00241 /* If not recognized, display as is. */ 00242 break; 00243 } 00244 break; 00245 00246 case 'XResolution': 00247 case 'YResolution': 00248 switch ( $resolutionunit ) { 00249 case 2: 00250 $val = $this->exifMsg( 'XYResolution', 'i', $this->formatNum( $val ) ); 00251 break; 00252 case 3: 00253 $val = $this->exifMsg( 'XYResolution', 'c', $this->formatNum( $val ) ); 00254 break; 00255 default: 00256 /* If not recognized, display as is. */ 00257 break; 00258 } 00259 break; 00260 00261 // TODO: YCbCrCoefficients #p27 (see annex E) 00262 case 'ExifVersion': 00263 case 'FlashpixVersion': 00264 $val = "$val" / 100; 00265 break; 00266 00267 case 'ColorSpace': 00268 switch ( $val ) { 00269 case 1: 00270 case 65535: 00271 $val = $this->exifMsg( $tag, $val ); 00272 break; 00273 default: 00274 /* If not recognized, display as is. */ 00275 break; 00276 } 00277 break; 00278 00279 case 'ComponentsConfiguration': 00280 switch ( $val ) { 00281 case 0: 00282 case 1: 00283 case 2: 00284 case 3: 00285 case 4: 00286 case 5: 00287 case 6: 00288 $val = $this->exifMsg( $tag, $val ); 00289 break; 00290 default: 00291 /* If not recognized, display as is. */ 00292 break; 00293 } 00294 break; 00295 00296 case 'DateTime': 00297 case 'DateTimeOriginal': 00298 case 'DateTimeDigitized': 00299 case 'DateTimeReleased': 00300 case 'DateTimeExpires': 00301 case 'GPSDateStamp': 00302 case 'dc-date': 00303 case 'DateTimeMetadata': 00304 if ( $val == '0000:00:00 00:00:00' || $val == ' : : : : ' ) { 00305 $val = $this->msg( 'exif-unknowndate' )->text(); 00306 } elseif ( preg_match( 00307 '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/D', 00308 $val 00309 ) ) { 00310 // Full date. 00311 $time = wfTimestamp( TS_MW, $val ); 00312 if ( $time && intval( $time ) > 0 ) { 00313 $val = $this->getLanguage()->timeanddate( $time ); 00314 } 00315 } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d)$/D', $val ) ) { 00316 // No second field. Still format the same 00317 // since timeanddate doesn't include seconds anyways, 00318 // but second still available in api 00319 $time = wfTimestamp( TS_MW, $val . ':00' ); 00320 if ( $time && intval( $time ) > 0 ) { 00321 $val = $this->getLanguage()->timeanddate( $time ); 00322 } 00323 } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d)$/D', $val ) ) { 00324 // If only the date but not the time is filled in. 00325 $time = wfTimestamp( TS_MW, substr( $val, 0, 4 ) 00326 . substr( $val, 5, 2 ) 00327 . substr( $val, 8, 2 ) 00328 . '000000' ); 00329 if ( $time && intval( $time ) > 0 ) { 00330 $val = $this->getLanguage()->date( $time ); 00331 } 00332 } 00333 // else it will just output $val without formatting it. 00334 break; 00335 00336 case 'ExposureProgram': 00337 switch ( $val ) { 00338 case 0: 00339 case 1: 00340 case 2: 00341 case 3: 00342 case 4: 00343 case 5: 00344 case 6: 00345 case 7: 00346 case 8: 00347 $val = $this->exifMsg( $tag, $val ); 00348 break; 00349 default: 00350 /* If not recognized, display as is. */ 00351 break; 00352 } 00353 break; 00354 00355 case 'SubjectDistance': 00356 $val = $this->exifMsg( $tag, '', $this->formatNum( $val ) ); 00357 break; 00358 00359 case 'MeteringMode': 00360 switch ( $val ) { 00361 case 0: 00362 case 1: 00363 case 2: 00364 case 3: 00365 case 4: 00366 case 5: 00367 case 6: 00368 case 7: 00369 case 255: 00370 $val = $this->exifMsg( $tag, $val ); 00371 break; 00372 default: 00373 /* If not recognized, display as is. */ 00374 break; 00375 } 00376 break; 00377 00378 case 'LightSource': 00379 switch ( $val ) { 00380 case 0: 00381 case 1: 00382 case 2: 00383 case 3: 00384 case 4: 00385 case 9: 00386 case 10: 00387 case 11: 00388 case 12: 00389 case 13: 00390 case 14: 00391 case 15: 00392 case 17: 00393 case 18: 00394 case 19: 00395 case 20: 00396 case 21: 00397 case 22: 00398 case 23: 00399 case 24: 00400 case 255: 00401 $val = $this->exifMsg( $tag, $val ); 00402 break; 00403 default: 00404 /* If not recognized, display as is. */ 00405 break; 00406 } 00407 break; 00408 00409 case 'Flash': 00410 $flashDecode = array( 00411 'fired' => $val & bindec( '00000001' ), 00412 'return' => ( $val & bindec( '00000110' ) ) >> 1, 00413 'mode' => ( $val & bindec( '00011000' ) ) >> 3, 00414 'function' => ( $val & bindec( '00100000' ) ) >> 5, 00415 'redeye' => ( $val & bindec( '01000000' ) ) >> 6, 00416 // 'reserved' => ($val & bindec( '10000000' )) >> 7, 00417 ); 00418 $flashMsgs = array(); 00419 # We do not need to handle unknown values since all are used. 00420 foreach ( $flashDecode as $subTag => $subValue ) { 00421 # We do not need any message for zeroed values. 00422 if ( $subTag != 'fired' && $subValue == 0 ) { 00423 continue; 00424 } 00425 $fullTag = $tag . '-' . $subTag; 00426 $flashMsgs[] = $this->exifMsg( $fullTag, $subValue ); 00427 } 00428 $val = $this->getLanguage()->commaList( $flashMsgs ); 00429 break; 00430 00431 case 'FocalPlaneResolutionUnit': 00432 switch ( $val ) { 00433 case 2: 00434 $val = $this->exifMsg( $tag, $val ); 00435 break; 00436 default: 00437 /* If not recognized, display as is. */ 00438 break; 00439 } 00440 break; 00441 00442 case 'SensingMethod': 00443 switch ( $val ) { 00444 case 1: 00445 case 2: 00446 case 3: 00447 case 4: 00448 case 5: 00449 case 7: 00450 case 8: 00451 $val = $this->exifMsg( $tag, $val ); 00452 break; 00453 default: 00454 /* If not recognized, display as is. */ 00455 break; 00456 } 00457 break; 00458 00459 case 'FileSource': 00460 switch ( $val ) { 00461 case 3: 00462 $val = $this->exifMsg( $tag, $val ); 00463 break; 00464 default: 00465 /* If not recognized, display as is. */ 00466 break; 00467 } 00468 break; 00469 00470 case 'SceneType': 00471 switch ( $val ) { 00472 case 1: 00473 $val = $this->exifMsg( $tag, $val ); 00474 break; 00475 default: 00476 /* If not recognized, display as is. */ 00477 break; 00478 } 00479 break; 00480 00481 case 'CustomRendered': 00482 switch ( $val ) { 00483 case 0: 00484 case 1: 00485 $val = $this->exifMsg( $tag, $val ); 00486 break; 00487 default: 00488 /* If not recognized, display as is. */ 00489 break; 00490 } 00491 break; 00492 00493 case 'ExposureMode': 00494 switch ( $val ) { 00495 case 0: 00496 case 1: 00497 case 2: 00498 $val = $this->exifMsg( $tag, $val ); 00499 break; 00500 default: 00501 /* If not recognized, display as is. */ 00502 break; 00503 } 00504 break; 00505 00506 case 'WhiteBalance': 00507 switch ( $val ) { 00508 case 0: 00509 case 1: 00510 $val = $this->exifMsg( $tag, $val ); 00511 break; 00512 default: 00513 /* If not recognized, display as is. */ 00514 break; 00515 } 00516 break; 00517 00518 case 'SceneCaptureType': 00519 switch ( $val ) { 00520 case 0: 00521 case 1: 00522 case 2: 00523 case 3: 00524 $val = $this->exifMsg( $tag, $val ); 00525 break; 00526 default: 00527 /* If not recognized, display as is. */ 00528 break; 00529 } 00530 break; 00531 00532 case 'GainControl': 00533 switch ( $val ) { 00534 case 0: 00535 case 1: 00536 case 2: 00537 case 3: 00538 case 4: 00539 $val = $this->exifMsg( $tag, $val ); 00540 break; 00541 default: 00542 /* If not recognized, display as is. */ 00543 break; 00544 } 00545 break; 00546 00547 case 'Contrast': 00548 switch ( $val ) { 00549 case 0: 00550 case 1: 00551 case 2: 00552 $val = $this->exifMsg( $tag, $val ); 00553 break; 00554 default: 00555 /* If not recognized, display as is. */ 00556 break; 00557 } 00558 break; 00559 00560 case 'Saturation': 00561 switch ( $val ) { 00562 case 0: 00563 case 1: 00564 case 2: 00565 $val = $this->exifMsg( $tag, $val ); 00566 break; 00567 default: 00568 /* If not recognized, display as is. */ 00569 break; 00570 } 00571 break; 00572 00573 case 'Sharpness': 00574 switch ( $val ) { 00575 case 0: 00576 case 1: 00577 case 2: 00578 $val = $this->exifMsg( $tag, $val ); 00579 break; 00580 default: 00581 /* If not recognized, display as is. */ 00582 break; 00583 } 00584 break; 00585 00586 case 'SubjectDistanceRange': 00587 switch ( $val ) { 00588 case 0: 00589 case 1: 00590 case 2: 00591 case 3: 00592 $val = $this->exifMsg( $tag, $val ); 00593 break; 00594 default: 00595 /* If not recognized, display as is. */ 00596 break; 00597 } 00598 break; 00599 00600 //The GPS...Ref values are kept for compatibility, probably won't be reached. 00601 case 'GPSLatitudeRef': 00602 case 'GPSDestLatitudeRef': 00603 switch ( $val ) { 00604 case 'N': 00605 case 'S': 00606 $val = $this->exifMsg( 'GPSLatitude', $val ); 00607 break; 00608 default: 00609 /* If not recognized, display as is. */ 00610 break; 00611 } 00612 break; 00613 00614 case 'GPSLongitudeRef': 00615 case 'GPSDestLongitudeRef': 00616 switch ( $val ) { 00617 case 'E': 00618 case 'W': 00619 $val = $this->exifMsg( 'GPSLongitude', $val ); 00620 break; 00621 default: 00622 /* If not recognized, display as is. */ 00623 break; 00624 } 00625 break; 00626 00627 case 'GPSAltitude': 00628 if ( $val < 0 ) { 00629 $val = $this->exifMsg( 'GPSAltitude', 'below-sealevel', $this->formatNum( -$val, 3 ) ); 00630 } else { 00631 $val = $this->exifMsg( 'GPSAltitude', 'above-sealevel', $this->formatNum( $val, 3 ) ); 00632 } 00633 break; 00634 00635 case 'GPSStatus': 00636 switch ( $val ) { 00637 case 'A': 00638 case 'V': 00639 $val = $this->exifMsg( $tag, $val ); 00640 break; 00641 default: 00642 /* If not recognized, display as is. */ 00643 break; 00644 } 00645 break; 00646 00647 case 'GPSMeasureMode': 00648 switch ( $val ) { 00649 case 2: 00650 case 3: 00651 $val = $this->exifMsg( $tag, $val ); 00652 break; 00653 default: 00654 /* If not recognized, display as is. */ 00655 break; 00656 } 00657 break; 00658 00659 case 'GPSTrackRef': 00660 case 'GPSImgDirectionRef': 00661 case 'GPSDestBearingRef': 00662 switch ( $val ) { 00663 case 'T': 00664 case 'M': 00665 $val = $this->exifMsg( 'GPSDirection', $val ); 00666 break; 00667 default: 00668 /* If not recognized, display as is. */ 00669 break; 00670 } 00671 break; 00672 00673 case 'GPSLatitude': 00674 case 'GPSDestLatitude': 00675 $val = $this->formatCoords( $val, 'latitude' ); 00676 break; 00677 case 'GPSLongitude': 00678 case 'GPSDestLongitude': 00679 $val = $this->formatCoords( $val, 'longitude' ); 00680 break; 00681 00682 case 'GPSSpeedRef': 00683 switch ( $val ) { 00684 case 'K': 00685 case 'M': 00686 case 'N': 00687 $val = $this->exifMsg( 'GPSSpeed', $val ); 00688 break; 00689 default: 00690 /* If not recognized, display as is. */ 00691 break; 00692 } 00693 break; 00694 00695 case 'GPSDestDistanceRef': 00696 switch ( $val ) { 00697 case 'K': 00698 case 'M': 00699 case 'N': 00700 $val = $this->exifMsg( 'GPSDestDistance', $val ); 00701 break; 00702 default: 00703 /* If not recognized, display as is. */ 00704 break; 00705 } 00706 break; 00707 00708 case 'GPSDOP': 00709 // See http://en.wikipedia.org/wiki/Dilution_of_precision_(GPS) 00710 if ( $val <= 2 ) { 00711 $val = $this->exifMsg( $tag, 'excellent', $this->formatNum( $val ) ); 00712 } elseif ( $val <= 5 ) { 00713 $val = $this->exifMsg( $tag, 'good', $this->formatNum( $val ) ); 00714 } elseif ( $val <= 10 ) { 00715 $val = $this->exifMsg( $tag, 'moderate', $this->formatNum( $val ) ); 00716 } elseif ( $val <= 20 ) { 00717 $val = $this->exifMsg( $tag, 'fair', $this->formatNum( $val ) ); 00718 } else { 00719 $val = $this->exifMsg( $tag, 'poor', $this->formatNum( $val ) ); 00720 } 00721 break; 00722 00723 // This is not in the Exif standard, just a special 00724 // case for our purposes which enables wikis to wikify 00725 // the make, model and software name to link to their articles. 00726 case 'Make': 00727 case 'Model': 00728 $val = $this->exifMsg( $tag, '', $val ); 00729 break; 00730 00731 case 'Software': 00732 if ( is_array( $val ) ) { 00733 //if its a software, version array. 00734 $val = $this->msg( 'exif-software-version-value', $val[0], $val[1] )->text(); 00735 } else { 00736 $val = $this->exifMsg( $tag, '', $val ); 00737 } 00738 break; 00739 00740 case 'ExposureTime': 00741 // Show the pretty fraction as well as decimal version 00742 $val = $this->msg( 'exif-exposuretime-format', 00743 $this->formatFraction( $val ), $this->formatNum( $val ) )->text(); 00744 break; 00745 case 'ISOSpeedRatings': 00746 // If its = 65535 that means its at the 00747 // limit of the size of Exif::short and 00748 // is really higher. 00749 if ( $val == '65535' ) { 00750 $val = $this->exifMsg( $tag, 'overflow' ); 00751 } else { 00752 $val = $this->formatNum( $val ); 00753 } 00754 break; 00755 case 'FNumber': 00756 $val = $this->msg( 'exif-fnumber-format', 00757 $this->formatNum( $val ) )->text(); 00758 break; 00759 00760 case 'FocalLength': 00761 case 'FocalLengthIn35mmFilm': 00762 $val = $this->msg( 'exif-focallength-format', 00763 $this->formatNum( $val ) )->text(); 00764 break; 00765 00766 case 'MaxApertureValue': 00767 if ( strpos( $val, '/' ) !== false ) { 00768 // need to expand this earlier to calculate fNumber 00769 list( $n, $d ) = explode( '/', $val ); 00770 if ( is_numeric( $n ) && is_numeric( $d ) ) { 00771 $val = $n / $d; 00772 } 00773 } 00774 if ( is_numeric( $val ) ) { 00775 $fNumber = pow( 2, $val / 2 ); 00776 if ( $fNumber !== false ) { 00777 $val = $this->msg( 'exif-maxaperturevalue-value', 00778 $this->formatNum( $val ), 00779 $this->formatNum( $fNumber, 2 ) 00780 )->text(); 00781 } 00782 } 00783 break; 00784 00785 case 'iimCategory': 00786 switch ( strtolower( $val ) ) { 00787 // See pg 29 of IPTC photo 00788 // metadata standard. 00789 case 'ace': 00790 case 'clj': 00791 case 'dis': 00792 case 'fin': 00793 case 'edu': 00794 case 'evn': 00795 case 'hth': 00796 case 'hum': 00797 case 'lab': 00798 case 'lif': 00799 case 'pol': 00800 case 'rel': 00801 case 'sci': 00802 case 'soi': 00803 case 'spo': 00804 case 'war': 00805 case 'wea': 00806 $val = $this->exifMsg( 00807 'iimcategory', 00808 $val 00809 ); 00810 } 00811 break; 00812 case 'SubjectNewsCode': 00813 // Essentially like iimCategory. 00814 // 8 (numeric) digit hierarchical 00815 // classification. We decode the 00816 // first 2 digits, which provide 00817 // a broad category. 00818 $val = $this->convertNewsCode( $val ); 00819 break; 00820 case 'Urgency': 00821 // 1-8 with 1 being highest, 5 normal 00822 // 0 is reserved, and 9 is 'user-defined'. 00823 $urgency = ''; 00824 if ( $val == 0 || $val == 9 ) { 00825 $urgency = 'other'; 00826 } elseif ( $val < 5 && $val > 1 ) { 00827 $urgency = 'high'; 00828 } elseif ( $val == 5 ) { 00829 $urgency = 'normal'; 00830 } elseif ( $val <= 8 && $val > 5 ) { 00831 $urgency = 'low'; 00832 } 00833 00834 if ( $urgency !== '' ) { 00835 $val = $this->exifMsg( 'urgency', 00836 $urgency, $val 00837 ); 00838 } 00839 break; 00840 00841 // Things that have a unit of pixels. 00842 case 'OriginalImageHeight': 00843 case 'OriginalImageWidth': 00844 case 'PixelXDimension': 00845 case 'PixelYDimension': 00846 case 'ImageWidth': 00847 case 'ImageLength': 00848 $val = $this->formatNum( $val ) . ' ' . $this->msg( 'unit-pixel' )->text(); 00849 break; 00850 00851 // Do not transform fields with pure text. 00852 // For some languages the formatNum() 00853 // conversion results to wrong output like 00854 // foo,bar@example,com or fooÙ«bar@exampleÙ«com. 00855 // Also some 'numeric' things like Scene codes 00856 // are included here as we really don't want 00857 // commas inserted. 00858 case 'ImageDescription': 00859 case 'Artist': 00860 case 'Copyright': 00861 case 'RelatedSoundFile': 00862 case 'ImageUniqueID': 00863 case 'SpectralSensitivity': 00864 case 'GPSSatellites': 00865 case 'GPSVersionID': 00866 case 'GPSMapDatum': 00867 case 'Keywords': 00868 case 'WorldRegionDest': 00869 case 'CountryDest': 00870 case 'CountryCodeDest': 00871 case 'ProvinceOrStateDest': 00872 case 'CityDest': 00873 case 'SublocationDest': 00874 case 'WorldRegionCreated': 00875 case 'CountryCreated': 00876 case 'CountryCodeCreated': 00877 case 'ProvinceOrStateCreated': 00878 case 'CityCreated': 00879 case 'SublocationCreated': 00880 case 'ObjectName': 00881 case 'SpecialInstructions': 00882 case 'Headline': 00883 case 'Credit': 00884 case 'Source': 00885 case 'EditStatus': 00886 case 'FixtureIdentifier': 00887 case 'LocationDest': 00888 case 'LocationDestCode': 00889 case 'Writer': 00890 case 'JPEGFileComment': 00891 case 'iimSupplementalCategory': 00892 case 'OriginalTransmissionRef': 00893 case 'Identifier': 00894 case 'dc-contributor': 00895 case 'dc-coverage': 00896 case 'dc-publisher': 00897 case 'dc-relation': 00898 case 'dc-rights': 00899 case 'dc-source': 00900 case 'dc-type': 00901 case 'Lens': 00902 case 'SerialNumber': 00903 case 'CameraOwnerName': 00904 case 'Label': 00905 case 'Nickname': 00906 case 'RightsCertificate': 00907 case 'CopyrightOwner': 00908 case 'UsageTerms': 00909 case 'WebStatement': 00910 case 'OriginalDocumentID': 00911 case 'LicenseUrl': 00912 case 'MorePermissionsUrl': 00913 case 'AttributionUrl': 00914 case 'PreferredAttributionName': 00915 case 'PNGFileComment': 00916 case 'Disclaimer': 00917 case 'ContentWarning': 00918 case 'GIFFileComment': 00919 case 'SceneCode': 00920 case 'IntellectualGenre': 00921 case 'Event': 00922 case 'OrginisationInImage': 00923 case 'PersonInImage': 00924 00925 $val = htmlspecialchars( $val ); 00926 break; 00927 00928 case 'ObjectCycle': 00929 switch ( $val ) { 00930 case 'a': 00931 case 'p': 00932 case 'b': 00933 $val = $this->exifMsg( $tag, $val ); 00934 break; 00935 default: 00936 $val = htmlspecialchars( $val ); 00937 break; 00938 } 00939 break; 00940 case 'Copyrighted': 00941 switch ( $val ) { 00942 case 'True': 00943 case 'False': 00944 $val = $this->exifMsg( $tag, $val ); 00945 break; 00946 } 00947 break; 00948 case 'Rating': 00949 if ( $val == '-1' ) { 00950 $val = $this->exifMsg( $tag, 'rejected' ); 00951 } else { 00952 $val = $this->formatNum( $val ); 00953 } 00954 break; 00955 00956 case 'LanguageCode': 00957 $lang = Language::fetchLanguageName( strtolower( $val ), $this->getLanguage()->getCode() ); 00958 if ( $lang ) { 00959 $val = htmlspecialchars( $lang ); 00960 } else { 00961 $val = htmlspecialchars( $val ); 00962 } 00963 break; 00964 00965 default: 00966 $val = $this->formatNum( $val ); 00967 break; 00968 } 00969 } 00970 // End formatting values, start flattening arrays. 00971 $vals = $this->flattenArrayReal( $vals, $type ); 00972 } 00973 00974 return $tags; 00975 } 00976 00991 public static function flattenArrayContentLang( $vals, $type = 'ul', 00992 $noHtml = false, $context = false 00993 ) { 00994 global $wgContLang; 00995 $obj = new FormatMetadata; 00996 if ( $context ) { 00997 $obj->setContext( $context ); 00998 } 00999 $context = new DerivativeContext( $obj->getContext() ); 01000 $context->setLanguage( $wgContLang ); 01001 $obj->setContext( $context ); 01002 01003 return $obj->flattenArrayReal( $vals, $type, $noHtml ); 01004 } 01005 01019 public static function flattenArray( $vals, $type = 'ul', $noHtml = false, $context = false ) { 01020 $obj = new FormatMetadata; 01021 if ( $context ) { 01022 $obj->setContext( $context ); 01023 } 01024 01025 return $obj->flattenArrayReal( $vals, $type, $noHtml ); 01026 } 01027 01044 public function flattenArrayReal( $vals, $type = 'ul', $noHtml = false ) { 01045 if ( !is_array( $vals ) ) { 01046 return $vals; // do nothing if not an array; 01047 } 01048 01049 if ( isset( $vals['_type'] ) ) { 01050 $type = $vals['_type']; 01051 unset( $vals['_type'] ); 01052 } 01053 01054 if ( !is_array( $vals ) ) { 01055 return $vals; // do nothing if not an array; 01056 } elseif ( count( $vals ) === 1 && $type !== 'lang' ) { 01057 return $vals[0]; 01058 } elseif ( count( $vals ) === 0 ) { 01059 wfDebug( __METHOD__ . " metadata array with 0 elements!\n" ); 01060 01061 return ""; // paranoia. This should never happen 01062 } else { 01063 /* @todo FIXME: This should hide some of the list entries if there are 01064 * say more than four. Especially if a field is translated into 20 01065 * languages, we don't want to show them all by default 01066 */ 01067 switch ( $type ) { 01068 case 'lang': 01069 // Display default, followed by ContLang, 01070 // followed by the rest in no particular 01071 // order. 01072 01073 // Todo: hide some items if really long list. 01074 01075 $content = ''; 01076 01077 $priorityLanguages = $this->getPriorityLanguages(); 01078 $defaultItem = false; 01079 $defaultLang = false; 01080 01081 // If default is set, save it for later, 01082 // as we don't know if it's equal to 01083 // one of the lang codes. (In xmp 01084 // you specify the language for a 01085 // default property by having both 01086 // a default prop, and one in the language 01087 // that are identical) 01088 if ( isset( $vals['x-default'] ) ) { 01089 $defaultItem = $vals['x-default']; 01090 unset( $vals['x-default'] ); 01091 } 01092 foreach ( $priorityLanguages as $pLang ) { 01093 if ( isset( $vals[$pLang] ) ) { 01094 $isDefault = false; 01095 if ( $vals[$pLang] === $defaultItem ) { 01096 $defaultItem = false; 01097 $isDefault = true; 01098 } 01099 $content .= $this->langItem( 01100 $vals[$pLang], $pLang, 01101 $isDefault, $noHtml ); 01102 01103 unset( $vals[$pLang] ); 01104 01105 if ( $this->singleLang ) { 01106 return Html::rawElement( 'span', 01107 array( 'lang' => $pLang ), $vals[$pLang] ); 01108 } 01109 } 01110 } 01111 01112 // Now do the rest. 01113 foreach ( $vals as $lang => $item ) { 01114 if ( $item === $defaultItem ) { 01115 $defaultLang = $lang; 01116 continue; 01117 } 01118 $content .= $this->langItem( $item, 01119 $lang, false, $noHtml ); 01120 if ( $this->singleLang ) { 01121 return Html::rawElement( 'span', 01122 array( 'lang' => $lang ), $item ); 01123 } 01124 } 01125 if ( $defaultItem !== false ) { 01126 $content = $this->langItem( $defaultItem, 01127 $defaultLang, true, $noHtml ) . 01128 $content; 01129 if ( $this->singleLang ) { 01130 return $defaultItem; 01131 } 01132 } 01133 if ( $noHtml ) { 01134 return $content; 01135 } 01136 01137 return '<ul class="metadata-langlist">' . 01138 $content . 01139 '</ul>'; 01140 case 'ol': 01141 if ( $noHtml ) { 01142 return "\n#" . implode( "\n#", $vals ); 01143 } 01144 01145 return "<ol><li>" . implode( "</li>\n<li>", $vals ) . '</li></ol>'; 01146 case 'ul': 01147 default: 01148 if ( $noHtml ) { 01149 return "\n*" . implode( "\n*", $vals ); 01150 } 01151 01152 return "<ul><li>" . implode( "</li>\n<li>", $vals ) . '</li></ul>'; 01153 } 01154 } 01155 } 01156 01167 private function langItem( $value, $lang, $default = false, $noHtml = false ) { 01168 if ( $lang === false && $default === false ) { 01169 throw new MWException( '$lang and $default cannot both ' 01170 . 'be false.' ); 01171 } 01172 01173 if ( $noHtml ) { 01174 $wrappedValue = $value; 01175 } else { 01176 $wrappedValue = '<span class="mw-metadata-lang-value">' 01177 . $value . '</span>'; 01178 } 01179 01180 if ( $lang === false ) { 01181 $msg = $this->msg( 'metadata-langitem-default', $wrappedValue ); 01182 if ( $noHtml ) { 01183 return $msg->text() . "\n\n"; 01184 } /* else */ 01185 01186 return '<li class="mw-metadata-lang-default">' 01187 . $msg->text() 01188 . "</li>\n"; 01189 } 01190 01191 $lowLang = strtolower( $lang ); 01192 $langName = Language::fetchLanguageName( $lowLang ); 01193 if ( $langName === '' ) { 01194 //try just the base language name. (aka en-US -> en ). 01195 list( $langPrefix ) = explode( '-', $lowLang, 2 ); 01196 $langName = Language::fetchLanguageName( $langPrefix ); 01197 if ( $langName === '' ) { 01198 // give up. 01199 $langName = $lang; 01200 } 01201 } 01202 // else we have a language specified 01203 01204 $msg = $this->msg( 'metadata-langitem', $wrappedValue, $langName, $lang ); 01205 if ( $noHtml ) { 01206 return '*' . $msg->text(); 01207 } /* else: */ 01208 01209 $item = '<li class="mw-metadata-lang-code-' 01210 . $lang; 01211 if ( $default ) { 01212 $item .= ' mw-metadata-lang-default'; 01213 } 01214 $item .= '" lang="' . $lang . '">'; 01215 $item .= $msg->text(); 01216 $item .= "</li>\n"; 01217 01218 return $item; 01219 } 01220 01230 private function exifMsg( $tag, $val, $arg = null, $arg2 = null ) { 01231 global $wgContLang; 01232 01233 if ( $val === '' ) { 01234 $val = 'value'; 01235 } 01236 01237 return $this->msg( $wgContLang->lc( "exif-$tag-$val" ), $arg, $arg2 )->text(); 01238 } 01239 01248 private function formatNum( $num, $round = false ) { 01249 $m = array(); 01250 if ( is_array( $num ) ) { 01251 $out = array(); 01252 foreach ( $num as $number ) { 01253 $out[] = $this->formatNum( $number ); 01254 } 01255 01256 return $this->getLanguage()->commaList( $out ); 01257 } 01258 if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) { 01259 if ( $m[2] != 0 ) { 01260 $newNum = $m[1] / $m[2]; 01261 if ( $round !== false ) { 01262 $newNum = round( $newNum, $round ); 01263 } 01264 } else { 01265 $newNum = $num; 01266 } 01267 01268 return $this->getLanguage()->formatNum( $newNum ); 01269 } else { 01270 if ( is_numeric( $num ) && $round !== false ) { 01271 $num = round( $num, $round ); 01272 } 01273 01274 return $this->getLanguage()->formatNum( $num ); 01275 } 01276 } 01277 01284 private function formatFraction( $num ) { 01285 $m = array(); 01286 if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) { 01287 $numerator = intval( $m[1] ); 01288 $denominator = intval( $m[2] ); 01289 $gcd = $this->gcd( abs( $numerator ), $denominator ); 01290 if ( $gcd != 0 ) { 01291 // 0 shouldn't happen! ;) 01292 return $this->formatNum( $numerator / $gcd ) . '/' . $this->formatNum( $denominator / $gcd ); 01293 } 01294 } 01295 01296 return $this->formatNum( $num ); 01297 } 01298 01307 private function gcd( $a, $b ) { 01308 /* 01309 // http://en.wikipedia.org/wiki/Euclidean_algorithm 01310 // Recursive form would be: 01311 if( $b == 0 ) 01312 return $a; 01313 else 01314 return gcd( $b, $a % $b ); 01315 */ 01316 while ( $b != 0 ) { 01317 $remainder = $a % $b; 01318 01319 // tail recursion... 01320 $a = $b; 01321 $b = $remainder; 01322 } 01323 01324 return $a; 01325 } 01326 01339 private function convertNewsCode( $val ) { 01340 if ( !preg_match( '/^\d{8}$/D', $val ) ) { 01341 // Not a valid news code. 01342 return $val; 01343 } 01344 $cat = ''; 01345 switch ( substr( $val, 0, 2 ) ) { 01346 case '01': 01347 $cat = 'ace'; 01348 break; 01349 case '02': 01350 $cat = 'clj'; 01351 break; 01352 case '03': 01353 $cat = 'dis'; 01354 break; 01355 case '04': 01356 $cat = 'fin'; 01357 break; 01358 case '05': 01359 $cat = 'edu'; 01360 break; 01361 case '06': 01362 $cat = 'evn'; 01363 break; 01364 case '07': 01365 $cat = 'hth'; 01366 break; 01367 case '08': 01368 $cat = 'hum'; 01369 break; 01370 case '09': 01371 $cat = 'lab'; 01372 break; 01373 case '10': 01374 $cat = 'lif'; 01375 break; 01376 case '11': 01377 $cat = 'pol'; 01378 break; 01379 case '12': 01380 $cat = 'rel'; 01381 break; 01382 case '13': 01383 $cat = 'sci'; 01384 break; 01385 case '14': 01386 $cat = 'soi'; 01387 break; 01388 case '15': 01389 $cat = 'spo'; 01390 break; 01391 case '16': 01392 $cat = 'war'; 01393 break; 01394 case '17': 01395 $cat = 'wea'; 01396 break; 01397 } 01398 if ( $cat !== '' ) { 01399 $catMsg = $this->exifMsg( 'iimcategory', $cat ); 01400 $val = $this->exifMsg( 'subjectnewscode', '', $val, $catMsg ); 01401 } 01402 01403 return $val; 01404 } 01405 01414 private function formatCoords( $coord, $type ) { 01415 $ref = ''; 01416 if ( $coord < 0 ) { 01417 $nCoord = -$coord; 01418 if ( $type === 'latitude' ) { 01419 $ref = 'S'; 01420 } elseif ( $type === 'longitude' ) { 01421 $ref = 'W'; 01422 } 01423 } else { 01424 $nCoord = $coord; 01425 if ( $type === 'latitude' ) { 01426 $ref = 'N'; 01427 } elseif ( $type === 'longitude' ) { 01428 $ref = 'E'; 01429 } 01430 } 01431 01432 $deg = floor( $nCoord ); 01433 $min = floor( ( $nCoord - $deg ) * 60.0 ); 01434 $sec = round( ( ( $nCoord - $deg ) - $min / 60 ) * 3600, 2 ); 01435 01436 $deg = $this->formatNum( $deg ); 01437 $min = $this->formatNum( $min ); 01438 $sec = $this->formatNum( $sec ); 01439 01440 return $this->msg( 'exif-coordinate-format', $deg, $min, $sec, $ref, $coord )->text(); 01441 } 01442 01457 public function collapseContactInfo( $vals ) { 01458 if ( !( isset( $vals['CiAdrExtadr'] ) 01459 || isset( $vals['CiAdrCity'] ) 01460 || isset( $vals['CiAdrCtry'] ) 01461 || isset( $vals['CiEmailWork'] ) 01462 || isset( $vals['CiTelWork'] ) 01463 || isset( $vals['CiAdrPcode'] ) 01464 || isset( $vals['CiAdrRegion'] ) 01465 || isset( $vals['CiUrlWork'] ) 01466 ) ) { 01467 // We don't have any sub-properties 01468 // This could happen if its using old 01469 // iptc that just had this as a free-form 01470 // text value. 01471 // Note: We run this through htmlspecialchars 01472 // partially to be consistent, and partially 01473 // because people often insert >, etc into 01474 // the metadata which should not be interpreted 01475 // but we still want to auto-link urls. 01476 foreach ( $vals as &$val ) { 01477 $val = htmlspecialchars( $val ); 01478 } 01479 01480 return $this->flattenArrayReal( $vals ); 01481 } else { 01482 // We have a real ContactInfo field. 01483 // Its unclear if all these fields have to be 01484 // set, so assume they do not. 01485 $url = $tel = $street = $city = $country = ''; 01486 $email = $postal = $region = ''; 01487 01488 // Also note, some of the class names this uses 01489 // are similar to those used by hCard. This is 01490 // mostly because they're sensible names. This 01491 // does not (and does not attempt to) output 01492 // stuff in the hCard microformat. However it 01493 // might output in the adr microformat. 01494 01495 if ( isset( $vals['CiAdrExtadr'] ) ) { 01496 // Todo: This can potentially be multi-line. 01497 // Need to check how that works in XMP. 01498 $street = '<span class="extended-address">' 01499 . htmlspecialchars( 01500 $vals['CiAdrExtadr'] ) 01501 . '</span>'; 01502 } 01503 if ( isset( $vals['CiAdrCity'] ) ) { 01504 $city = '<span class="locality">' 01505 . htmlspecialchars( $vals['CiAdrCity'] ) 01506 . '</span>'; 01507 } 01508 if ( isset( $vals['CiAdrCtry'] ) ) { 01509 $country = '<span class="country-name">' 01510 . htmlspecialchars( $vals['CiAdrCtry'] ) 01511 . '</span>'; 01512 } 01513 if ( isset( $vals['CiEmailWork'] ) ) { 01514 $emails = array(); 01515 // Have to split multiple emails at commas/new lines. 01516 $splitEmails = explode( "\n", $vals['CiEmailWork'] ); 01517 foreach ( $splitEmails as $e1 ) { 01518 // Also split on comma 01519 foreach ( explode( ',', $e1 ) as $e2 ) { 01520 $finalEmail = trim( $e2 ); 01521 if ( $finalEmail == ',' || $finalEmail == '' ) { 01522 continue; 01523 } 01524 if ( strpos( $finalEmail, '<' ) !== false ) { 01525 // Don't do fancy formatting to 01526 // "My name" <[email protected]> style stuff 01527 $emails[] = $finalEmail; 01528 } else { 01529 $emails[] = '[mailto:' 01530 . $finalEmail 01531 . ' <span class="email">' 01532 . $finalEmail 01533 . '</span>]'; 01534 } 01535 } 01536 } 01537 $email = implode( ', ', $emails ); 01538 } 01539 if ( isset( $vals['CiTelWork'] ) ) { 01540 $tel = '<span class="tel">' 01541 . htmlspecialchars( $vals['CiTelWork'] ) 01542 . '</span>'; 01543 } 01544 if ( isset( $vals['CiAdrPcode'] ) ) { 01545 $postal = '<span class="postal-code">' 01546 . htmlspecialchars( 01547 $vals['CiAdrPcode'] ) 01548 . '</span>'; 01549 } 01550 if ( isset( $vals['CiAdrRegion'] ) ) { 01551 // Note this is province/state. 01552 $region = '<span class="region">' 01553 . htmlspecialchars( 01554 $vals['CiAdrRegion'] ) 01555 . '</span>'; 01556 } 01557 if ( isset( $vals['CiUrlWork'] ) ) { 01558 $url = '<span class="url">' 01559 . htmlspecialchars( $vals['CiUrlWork'] ) 01560 . '</span>'; 01561 } 01562 01563 return $this->msg( 'exif-contact-value', $email, $url, 01564 $street, $city, $region, $postal, $country, 01565 $tel )->text(); 01566 } 01567 } 01568 01575 public static function getVisibleFields() { 01576 $fields = array(); 01577 $lines = explode( "\n", wfMessage( 'metadata-fields' )->inContentLanguage()->text() ); 01578 foreach ( $lines as $line ) { 01579 $matches = array(); 01580 if ( preg_match( '/^\\*\s*(.*?)\s*$/', $line, $matches ) ) { 01581 $fields[] = $matches[1]; 01582 } 01583 } 01584 $fields = array_map( 'strtolower', $fields ); 01585 01586 return $fields; 01587 } 01588 01596 public function fetchExtendedMetadata( File $file ) { 01597 global $wgMemc; 01598 01599 wfProfileIn( __METHOD__ ); 01600 01601 // If revision deleted, exit immediately 01602 if ( $file->isDeleted( File::DELETED_FILE ) ) { 01603 wfProfileOut( __METHOD__ ); 01604 01605 return array(); 01606 } 01607 01608 $cacheKey = wfMemcKey( 01609 'getExtendedMetadata', 01610 $this->getLanguage()->getCode(), 01611 (int)$this->singleLang, 01612 $file->getSha1() 01613 ); 01614 01615 $cachedValue = $wgMemc->get( $cacheKey ); 01616 if ( 01617 $cachedValue 01618 && wfRunHooks( 'ValidateExtendedMetadataCache', array( $cachedValue['timestamp'], $file ) ) 01619 ) { 01620 $extendedMetadata = $cachedValue['data']; 01621 } else { 01622 $maxCacheTime = ( $file instanceof ForeignAPIFile ) ? 60 * 60 * 12 : 60 * 60 * 24 * 30; 01623 $fileMetadata = $this->getExtendedMetadataFromFile( $file ); 01624 $extendedMetadata = $this->getExtendedMetadataFromHook( $file, $fileMetadata, $maxCacheTime ); 01625 if ( $this->singleLang ) { 01626 $this->resolveMultilangMetadata( $extendedMetadata ); 01627 } 01628 // Make sure the metadata won't break the API when an XML format is used. 01629 // This is an API-specific function so it would be cleaner to call it from 01630 // outside fetchExtendedMetadata, but this way we don't need to redo the 01631 // computation on a cache hit. 01632 $this->sanitizeArrayForXml( $extendedMetadata ); 01633 $valueToCache = array( 'data' => $extendedMetadata, 'timestamp' => wfTimestampNow() ); 01634 $wgMemc->set( $cacheKey, $valueToCache, $maxCacheTime ); 01635 } 01636 01637 wfProfileOut( __METHOD__ ); 01638 01639 return $extendedMetadata; 01640 } 01641 01651 protected function getExtendedMetadataFromFile( File $file ) { 01652 // If this is a remote file accessed via an API request, we already 01653 // have remote metadata so we just ignore any local one 01654 if ( $file instanceof ForeignAPIFile ) { 01655 // In case of error we pretend no metadata - this will get cached. 01656 // Might or might not be a good idea. 01657 return $file->getExtendedMetadata() ?: array(); 01658 } 01659 01660 wfProfileIn( __METHOD__ ); 01661 01662 $uploadDate = wfTimestamp( TS_ISO_8601, $file->getTimestamp() ); 01663 01664 $fileMetadata = array( 01665 // This is modification time, which is close to "upload" time. 01666 'DateTime' => array( 01667 'value' => $uploadDate, 01668 'source' => 'mediawiki-metadata', 01669 ), 01670 ); 01671 01672 $title = $file->getTitle(); 01673 if ( $title ) { 01674 $text = $title->getText(); 01675 $pos = strrpos( $text, '.' ); 01676 01677 if ( $pos ) { 01678 $name = substr( $text, 0, $pos ); 01679 } else { 01680 $name = $text; 01681 } 01682 01683 $fileMetadata['ObjectName'] = array( 01684 'value' => $name, 01685 'source' => 'mediawiki-metadata', 01686 ); 01687 } 01688 01689 $common = $file->getCommonMetaArray(); 01690 01691 if ( $common !== false ) { 01692 foreach ( $common as $key => $value ) { 01693 $fileMetadata[$key] = array( 01694 'value' => $value, 01695 'source' => 'file-metadata', 01696 ); 01697 } 01698 } 01699 01700 wfProfileOut( __METHOD__ ); 01701 01702 return $fileMetadata; 01703 } 01704 01715 protected function getExtendedMetadataFromHook( File $file, array $extendedMetadata, 01716 &$maxCacheTime 01717 ) { 01718 wfProfileIn( __METHOD__ ); 01719 01720 wfRunHooks( 'GetExtendedMetadata', array( 01721 &$extendedMetadata, 01722 $file, 01723 $this->getContext(), 01724 $this->singleLang, 01725 &$maxCacheTime 01726 ) ); 01727 01728 $visible = array_flip( self::getVisibleFields() ); 01729 foreach ( $extendedMetadata as $key => $value ) { 01730 if ( !isset( $visible[strtolower( $key )] ) ) { 01731 $extendedMetadata[$key]['hidden'] = ''; 01732 } 01733 } 01734 01735 wfProfileOut( __METHOD__ ); 01736 01737 return $extendedMetadata; 01738 } 01739 01748 protected function resolveMultilangValue( $value ) { 01749 if ( 01750 !is_array( $value ) 01751 || !isset( $value['_type'] ) 01752 || $value['_type'] != 'lang' 01753 ) { 01754 return $value; // do nothing if not a multilang array 01755 } 01756 01757 // choose the language best matching user or site settings 01758 $priorityLanguages = $this->getPriorityLanguages(); 01759 foreach ( $priorityLanguages as $lang ) { 01760 if ( isset( $value[$lang] ) ) { 01761 return $value[$lang]; 01762 } 01763 } 01764 01765 // otherwise go with the default language, if set 01766 if ( isset( $value['x-default'] ) ) { 01767 return $value['x-default']; 01768 } 01769 01770 // otherwise just return any one language 01771 unset( $value['_type'] ); 01772 if ( !empty( $value ) ) { 01773 return reset( $value ); 01774 } 01775 01776 // this should not happen; signal error 01777 return null; 01778 } 01779 01786 protected function resolveMultilangMetadata( &$metadata ) { 01787 if ( !is_array( $metadata ) ) { 01788 return; 01789 } 01790 foreach ( $metadata as &$field ) { 01791 if ( isset( $field['value'] ) ) { 01792 $field['value'] = $this->resolveMultilangValue( $field['value'] ); 01793 } 01794 } 01795 } 01796 01802 protected function sanitizeArrayForXml( &$arr ) { 01803 if ( !is_array( $arr ) ) { 01804 return; 01805 } 01806 01807 $counter = 1; 01808 foreach ( $arr as $key => &$value ) { 01809 $sanitizedKey = $this->sanitizeKeyForXml( $key ); 01810 if ( $sanitizedKey !== $key ) { 01811 if ( isset( $arr[$sanitizedKey] ) ) { 01812 // Make the sanitized keys hopefully unique. 01813 // To make it definitely unique would be too much effort, given that 01814 // sanitizing is only needed for misformatted metadata anyway, but 01815 // this at least covers the case when $arr is numeric. 01816 $sanitizedKey .= $counter; 01817 ++$counter; 01818 } 01819 $arr[$sanitizedKey] = $arr[$key]; 01820 unset( $arr[$key] ); 01821 } 01822 if ( is_array( $value ) ) { 01823 $this->sanitizeArrayForXml( $value ); 01824 } 01825 } 01826 } 01827 01836 protected function sanitizeKeyForXml( $key ) { 01837 // drop all characters which are not valid in an XML tag name 01838 // a bunch of non-ASCII letters would be valid but probably won't 01839 // be used so we take the easy way 01840 $key = preg_replace( '/[^a-zA-z0-9_:.-]/', '', $key ); 01841 // drop characters which are invalid at the first position 01842 $key = preg_replace( '/^[\d-.]+/', '', $key ); 01843 01844 if ( $key == '' ) { 01845 $key = '_'; 01846 } 01847 01848 // special case for an internal keyword 01849 if ( $key == '_element' ) { 01850 $key = 'element'; 01851 } 01852 01853 return $key; 01854 } 01855 01862 protected function getPriorityLanguages() { 01863 $priorityLanguages = 01864 Language::getFallbacksIncludingSiteLanguage( $this->getLanguage()->getCode() ); 01865 $priorityLanguages = array_merge( 01866 (array)$this->getLanguage()->getCode(), 01867 $priorityLanguages[0], 01868 $priorityLanguages[1] 01869 ); 01870 01871 return $priorityLanguages; 01872 } 01873 } 01874 01881 class FormatExif { 01883 private $meta; 01884 01888 function __construct( $meta ) { 01889 wfDeprecated( __METHOD__, '1.18' ); 01890 $this->meta = $meta; 01891 } 01892 01896 function getFormattedData() { 01897 return FormatMetadata::getFormattedData( $this->meta ); 01898 } 01899 }