MediaWiki  REL1_23
FormatMetadata.php
Go to the documentation of this file.
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 }