MediaWiki  REL1_24
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 
01306     private function gcd( $a, $b ) {
01307         /*
01308             // http://en.wikipedia.org/wiki/Euclidean_algorithm
01309             // Recursive form would be:
01310             if( $b == 0 )
01311                 return $a;
01312             else
01313                 return gcd( $b, $a % $b );
01314         */
01315         while ( $b != 0 ) {
01316             $remainder = $a % $b;
01317 
01318             // tail recursion...
01319             $a = $b;
01320             $b = $remainder;
01321         }
01322 
01323         return $a;
01324     }
01325 
01338     private function convertNewsCode( $val ) {
01339         if ( !preg_match( '/^\d{8}$/D', $val ) ) {
01340             // Not a valid news code.
01341             return $val;
01342         }
01343         $cat = '';
01344         switch ( substr( $val, 0, 2 ) ) {
01345             case '01':
01346                 $cat = 'ace';
01347                 break;
01348             case '02':
01349                 $cat = 'clj';
01350                 break;
01351             case '03':
01352                 $cat = 'dis';
01353                 break;
01354             case '04':
01355                 $cat = 'fin';
01356                 break;
01357             case '05':
01358                 $cat = 'edu';
01359                 break;
01360             case '06':
01361                 $cat = 'evn';
01362                 break;
01363             case '07':
01364                 $cat = 'hth';
01365                 break;
01366             case '08':
01367                 $cat = 'hum';
01368                 break;
01369             case '09':
01370                 $cat = 'lab';
01371                 break;
01372             case '10':
01373                 $cat = 'lif';
01374                 break;
01375             case '11':
01376                 $cat = 'pol';
01377                 break;
01378             case '12':
01379                 $cat = 'rel';
01380                 break;
01381             case '13':
01382                 $cat = 'sci';
01383                 break;
01384             case '14':
01385                 $cat = 'soi';
01386                 break;
01387             case '15':
01388                 $cat = 'spo';
01389                 break;
01390             case '16':
01391                 $cat = 'war';
01392                 break;
01393             case '17':
01394                 $cat = 'wea';
01395                 break;
01396         }
01397         if ( $cat !== '' ) {
01398             $catMsg = $this->exifMsg( 'iimcategory', $cat );
01399             $val = $this->exifMsg( 'subjectnewscode', '', $val, $catMsg );
01400         }
01401 
01402         return $val;
01403     }
01404 
01413     private function formatCoords( $coord, $type ) {
01414         $ref = '';
01415         if ( $coord < 0 ) {
01416             $nCoord = -$coord;
01417             if ( $type === 'latitude' ) {
01418                 $ref = 'S';
01419             } elseif ( $type === 'longitude' ) {
01420                 $ref = 'W';
01421             }
01422         } else {
01423             $nCoord = $coord;
01424             if ( $type === 'latitude' ) {
01425                 $ref = 'N';
01426             } elseif ( $type === 'longitude' ) {
01427                 $ref = 'E';
01428             }
01429         }
01430 
01431         $deg = floor( $nCoord );
01432         $min = floor( ( $nCoord - $deg ) * 60.0 );
01433         $sec = round( ( ( $nCoord - $deg ) - $min / 60 ) * 3600, 2 );
01434 
01435         $deg = $this->formatNum( $deg );
01436         $min = $this->formatNum( $min );
01437         $sec = $this->formatNum( $sec );
01438 
01439         return $this->msg( 'exif-coordinate-format', $deg, $min, $sec, $ref, $coord )->text();
01440     }
01441 
01456     public function collapseContactInfo( $vals ) {
01457         if ( !( isset( $vals['CiAdrExtadr'] )
01458             || isset( $vals['CiAdrCity'] )
01459             || isset( $vals['CiAdrCtry'] )
01460             || isset( $vals['CiEmailWork'] )
01461             || isset( $vals['CiTelWork'] )
01462             || isset( $vals['CiAdrPcode'] )
01463             || isset( $vals['CiAdrRegion'] )
01464             || isset( $vals['CiUrlWork'] )
01465         ) ) {
01466             // We don't have any sub-properties
01467             // This could happen if its using old
01468             // iptc that just had this as a free-form
01469             // text value.
01470             // Note: We run this through htmlspecialchars
01471             // partially to be consistent, and partially
01472             // because people often insert >, etc into
01473             // the metadata which should not be interpreted
01474             // but we still want to auto-link urls.
01475             foreach ( $vals as &$val ) {
01476                 $val = htmlspecialchars( $val );
01477             }
01478 
01479             return $this->flattenArrayReal( $vals );
01480         } else {
01481             // We have a real ContactInfo field.
01482             // Its unclear if all these fields have to be
01483             // set, so assume they do not.
01484             $url = $tel = $street = $city = $country = '';
01485             $email = $postal = $region = '';
01486 
01487             // Also note, some of the class names this uses
01488             // are similar to those used by hCard. This is
01489             // mostly because they're sensible names. This
01490             // does not (and does not attempt to) output
01491             // stuff in the hCard microformat. However it
01492             // might output in the adr microformat.
01493 
01494             if ( isset( $vals['CiAdrExtadr'] ) ) {
01495                 // Todo: This can potentially be multi-line.
01496                 // Need to check how that works in XMP.
01497                 $street = '<span class="extended-address">'
01498                     . htmlspecialchars(
01499                         $vals['CiAdrExtadr'] )
01500                     . '</span>';
01501             }
01502             if ( isset( $vals['CiAdrCity'] ) ) {
01503                 $city = '<span class="locality">'
01504                     . htmlspecialchars( $vals['CiAdrCity'] )
01505                     . '</span>';
01506             }
01507             if ( isset( $vals['CiAdrCtry'] ) ) {
01508                 $country = '<span class="country-name">'
01509                     . htmlspecialchars( $vals['CiAdrCtry'] )
01510                     . '</span>';
01511             }
01512             if ( isset( $vals['CiEmailWork'] ) ) {
01513                 $emails = array();
01514                 // Have to split multiple emails at commas/new lines.
01515                 $splitEmails = explode( "\n", $vals['CiEmailWork'] );
01516                 foreach ( $splitEmails as $e1 ) {
01517                     // Also split on comma
01518                     foreach ( explode( ',', $e1 ) as $e2 ) {
01519                         $finalEmail = trim( $e2 );
01520                         if ( $finalEmail == ',' || $finalEmail == '' ) {
01521                             continue;
01522                         }
01523                         if ( strpos( $finalEmail, '<' ) !== false ) {
01524                             // Don't do fancy formatting to
01525                             // "My name" <[email protected]> style stuff
01526                             $emails[] = $finalEmail;
01527                         } else {
01528                             $emails[] = '[mailto:'
01529                                 . $finalEmail
01530                                 . ' <span class="email">'
01531                                 . $finalEmail
01532                                 . '</span>]';
01533                         }
01534                     }
01535                 }
01536                 $email = implode( ', ', $emails );
01537             }
01538             if ( isset( $vals['CiTelWork'] ) ) {
01539                 $tel = '<span class="tel">'
01540                     . htmlspecialchars( $vals['CiTelWork'] )
01541                     . '</span>';
01542             }
01543             if ( isset( $vals['CiAdrPcode'] ) ) {
01544                 $postal = '<span class="postal-code">'
01545                     . htmlspecialchars(
01546                         $vals['CiAdrPcode'] )
01547                     . '</span>';
01548             }
01549             if ( isset( $vals['CiAdrRegion'] ) ) {
01550                 // Note this is province/state.
01551                 $region = '<span class="region">'
01552                     . htmlspecialchars(
01553                         $vals['CiAdrRegion'] )
01554                     . '</span>';
01555             }
01556             if ( isset( $vals['CiUrlWork'] ) ) {
01557                 $url = '<span class="url">'
01558                     . htmlspecialchars( $vals['CiUrlWork'] )
01559                     . '</span>';
01560             }
01561 
01562             return $this->msg( 'exif-contact-value', $email, $url,
01563                 $street, $city, $region, $postal, $country,
01564                 $tel )->text();
01565         }
01566     }
01567 
01574     public static function getVisibleFields() {
01575         $fields = array();
01576         $lines = explode( "\n", wfMessage( 'metadata-fields' )->inContentLanguage()->text() );
01577         foreach ( $lines as $line ) {
01578             $matches = array();
01579             if ( preg_match( '/^\\*\s*(.*?)\s*$/', $line, $matches ) ) {
01580                 $fields[] = $matches[1];
01581             }
01582         }
01583         $fields = array_map( 'strtolower', $fields );
01584 
01585         return $fields;
01586     }
01587 
01595     public function fetchExtendedMetadata( File $file ) {
01596         global $wgMemc;
01597 
01598         wfProfileIn( __METHOD__ );
01599 
01600         // If revision deleted, exit immediately
01601         if ( $file->isDeleted( File::DELETED_FILE ) ) {
01602             wfProfileOut( __METHOD__ );
01603 
01604             return array();
01605         }
01606 
01607         $cacheKey = wfMemcKey(
01608             'getExtendedMetadata',
01609             $this->getLanguage()->getCode(),
01610             (int)$this->singleLang,
01611             $file->getSha1()
01612         );
01613 
01614         $cachedValue = $wgMemc->get( $cacheKey );
01615         if (
01616             $cachedValue
01617             && wfRunHooks( 'ValidateExtendedMetadataCache', array( $cachedValue['timestamp'], $file ) )
01618         ) {
01619             $extendedMetadata = $cachedValue['data'];
01620         } else {
01621             $maxCacheTime = ( $file instanceof ForeignAPIFile ) ? 60 * 60 * 12 : 60 * 60 * 24 * 30;
01622             $fileMetadata = $this->getExtendedMetadataFromFile( $file );
01623             $extendedMetadata = $this->getExtendedMetadataFromHook( $file, $fileMetadata, $maxCacheTime );
01624             if ( $this->singleLang ) {
01625                 $this->resolveMultilangMetadata( $extendedMetadata );
01626             }
01627             // Make sure the metadata won't break the API when an XML format is used.
01628             // This is an API-specific function so it would be cleaner to call it from
01629             // outside fetchExtendedMetadata, but this way we don't need to redo the
01630             // computation on a cache hit.
01631             $this->sanitizeArrayForXml( $extendedMetadata );
01632             $valueToCache = array( 'data' => $extendedMetadata, 'timestamp' => wfTimestampNow() );
01633             $wgMemc->set( $cacheKey, $valueToCache, $maxCacheTime );
01634         }
01635 
01636         wfProfileOut( __METHOD__ );
01637 
01638         return $extendedMetadata;
01639     }
01640 
01650     protected function getExtendedMetadataFromFile( File $file ) {
01651         // If this is a remote file accessed via an API request, we already
01652         // have remote metadata so we just ignore any local one
01653         if ( $file instanceof ForeignAPIFile ) {
01654             // In case of error we pretend no metadata - this will get cached.
01655             // Might or might not be a good idea.
01656             return $file->getExtendedMetadata() ?: array();
01657         }
01658 
01659         wfProfileIn( __METHOD__ );
01660 
01661         $uploadDate = wfTimestamp( TS_ISO_8601, $file->getTimestamp() );
01662 
01663         $fileMetadata = array(
01664             // This is modification time, which is close to "upload" time.
01665             'DateTime' => array(
01666                 'value' => $uploadDate,
01667                 'source' => 'mediawiki-metadata',
01668             ),
01669         );
01670 
01671         $title = $file->getTitle();
01672         if ( $title ) {
01673             $text = $title->getText();
01674             $pos = strrpos( $text, '.' );
01675 
01676             if ( $pos ) {
01677                 $name = substr( $text, 0, $pos );
01678             } else {
01679                 $name = $text;
01680             }
01681 
01682             $fileMetadata['ObjectName'] = array(
01683                 'value' => $name,
01684                 'source' => 'mediawiki-metadata',
01685             );
01686         }
01687 
01688         $common = $file->getCommonMetaArray();
01689 
01690         if ( $common !== false ) {
01691             foreach ( $common as $key => $value ) {
01692                 $fileMetadata[$key] = array(
01693                     'value' => $value,
01694                     'source' => 'file-metadata',
01695                 );
01696             }
01697         }
01698 
01699         wfProfileOut( __METHOD__ );
01700 
01701         return $fileMetadata;
01702     }
01703 
01714     protected function getExtendedMetadataFromHook( File $file, array $extendedMetadata,
01715         &$maxCacheTime
01716     ) {
01717         wfProfileIn( __METHOD__ );
01718 
01719         wfRunHooks( 'GetExtendedMetadata', array(
01720             &$extendedMetadata,
01721             $file,
01722             $this->getContext(),
01723             $this->singleLang,
01724             &$maxCacheTime
01725         ) );
01726 
01727         $visible = array_flip( self::getVisibleFields() );
01728         foreach ( $extendedMetadata as $key => $value ) {
01729             if ( !isset( $visible[strtolower( $key )] ) ) {
01730                 $extendedMetadata[$key]['hidden'] = '';
01731             }
01732         }
01733 
01734         wfProfileOut( __METHOD__ );
01735 
01736         return $extendedMetadata;
01737     }
01738 
01747     protected function resolveMultilangValue( $value ) {
01748         if (
01749             !is_array( $value )
01750             || !isset( $value['_type'] )
01751             || $value['_type'] != 'lang'
01752         ) {
01753             return $value; // do nothing if not a multilang array
01754         }
01755 
01756         // choose the language best matching user or site settings
01757         $priorityLanguages = $this->getPriorityLanguages();
01758         foreach ( $priorityLanguages as $lang ) {
01759             if ( isset( $value[$lang] ) ) {
01760                 return $value[$lang];
01761             }
01762         }
01763 
01764         // otherwise go with the default language, if set
01765         if ( isset( $value['x-default'] ) ) {
01766             return $value['x-default'];
01767         }
01768 
01769         // otherwise just return any one language
01770         unset( $value['_type'] );
01771         if ( !empty( $value ) ) {
01772             return reset( $value );
01773         }
01774 
01775         // this should not happen; signal error
01776         return null;
01777     }
01778 
01785     protected function resolveMultilangMetadata( &$metadata ) {
01786         if ( !is_array( $metadata ) ) {
01787             return;
01788         }
01789         foreach ( $metadata as &$field ) {
01790             if ( isset( $field['value'] ) ) {
01791                 $field['value'] = $this->resolveMultilangValue( $field['value'] );
01792             }
01793         }
01794     }
01795 
01801     protected function sanitizeArrayForXml( &$arr ) {
01802         if ( !is_array( $arr ) ) {
01803             return;
01804         }
01805 
01806         $counter = 1;
01807         foreach ( $arr as $key => &$value ) {
01808             $sanitizedKey = $this->sanitizeKeyForXml( $key );
01809             if ( $sanitizedKey !== $key ) {
01810                 if ( isset( $arr[$sanitizedKey] ) ) {
01811                     // Make the sanitized keys hopefully unique.
01812                     // To make it definitely unique would be too much effort, given that
01813                     // sanitizing is only needed for misformatted metadata anyway, but
01814                     // this at least covers the case when $arr is numeric.
01815                     $sanitizedKey .= $counter;
01816                     ++$counter;
01817                 }
01818                 $arr[$sanitizedKey] = $arr[$key];
01819                 unset( $arr[$key] );
01820             }
01821             if ( is_array( $value ) ) {
01822                 $this->sanitizeArrayForXml( $value );
01823             }
01824         }
01825     }
01826 
01835     protected function sanitizeKeyForXml( $key ) {
01836         // drop all characters which are not valid in an XML tag name
01837         // a bunch of non-ASCII letters would be valid but probably won't
01838         // be used so we take the easy way
01839         $key = preg_replace( '/[^a-zA-z0-9_:.-]/', '', $key );
01840         // drop characters which are invalid at the first position
01841         $key = preg_replace( '/^[\d-.]+/', '', $key );
01842 
01843         if ( $key == '' ) {
01844             $key = '_';
01845         }
01846 
01847         // special case for an internal keyword
01848         if ( $key == '_element' ) {
01849             $key = 'element';
01850         }
01851 
01852         return $key;
01853     }
01854 
01861     protected function getPriorityLanguages() {
01862         $priorityLanguages =
01863             Language::getFallbacksIncludingSiteLanguage( $this->getLanguage()->getCode() );
01864         $priorityLanguages = array_merge(
01865             (array)$this->getLanguage()->getCode(),
01866             $priorityLanguages[0],
01867             $priorityLanguages[1]
01868         );
01869 
01870         return $priorityLanguages;
01871     }
01872 }