MediaWiki  REL1_31
FormatMetadata.php
Go to the documentation of this file.
1 <?php
28 use Wikimedia\Timestamp\TimestampException;
29 
57  protected $singleLang = false;
58 
65  public function setSingleLanguage( $val ) {
66  $this->singleLang = $val;
67  }
68 
82  public static function getFormattedData( $tags, $context = false ) {
83  $obj = new FormatMetadata;
84  if ( $context ) {
85  $obj->setContext( $context );
86  }
87 
88  return $obj->makeFormattedData( $tags );
89  }
90 
102  public function makeFormattedData( $tags ) {
103  $resolutionunit = !isset( $tags['ResolutionUnit'] ) || $tags['ResolutionUnit'] == 2 ? 2 : 3;
104  unset( $tags['ResolutionUnit'] );
105 
106  foreach ( $tags as $tag => &$vals ) {
107  // This seems ugly to wrap non-array's in an array just to unwrap again,
108  // especially when most of the time it is not an array
109  if ( !is_array( $tags[$tag] ) ) {
110  $vals = [ $vals ];
111  }
112 
113  // _type is a special value to say what array type
114  if ( isset( $tags[$tag]['_type'] ) ) {
115  $type = $tags[$tag]['_type'];
116  unset( $vals['_type'] );
117  } else {
118  $type = 'ul'; // default unordered list.
119  }
120 
121  // This is done differently as the tag is an array.
122  if ( $tag == 'GPSTimeStamp' && count( $vals ) === 3 ) {
123  // hour min sec array
124 
125  $h = explode( '/', $vals[0] );
126  $m = explode( '/', $vals[1] );
127  $s = explode( '/', $vals[2] );
128 
129  // this should already be validated
130  // when loaded from file, but it could
131  // come from a foreign repo, so be
132  // paranoid.
133  if ( !isset( $h[1] )
134  || !isset( $m[1] )
135  || !isset( $s[1] )
136  || $h[1] == 0
137  || $m[1] == 0
138  || $s[1] == 0
139  ) {
140  continue;
141  }
142  $tags[$tag] = str_pad( intval( $h[0] / $h[1] ), 2, '0', STR_PAD_LEFT )
143  . ':' . str_pad( intval( $m[0] / $m[1] ), 2, '0', STR_PAD_LEFT )
144  . ':' . str_pad( intval( $s[0] / $s[1] ), 2, '0', STR_PAD_LEFT );
145 
146  try {
147  $time = wfTimestamp( TS_MW, '1971:01:01 ' . $tags[$tag] );
148  // the 1971:01:01 is just a placeholder, and not shown to user.
149  if ( $time && intval( $time ) > 0 ) {
150  $tags[$tag] = $this->getLanguage()->time( $time );
151  }
152  } catch ( TimestampException $e ) {
153  // This shouldn't happen, but we've seen bad formats
154  // such as 4-digit seconds in the wild.
155  // leave $tags[$tag] as-is
156  }
157  continue;
158  }
159 
160  // The contact info is a multi-valued field
161  // instead of the other props which are single
162  // valued (mostly) so handle as a special case.
163  if ( $tag === 'Contact' ) {
164  $vals = $this->collapseContactInfo( $vals );
165  continue;
166  }
167 
168  foreach ( $vals as &$val ) {
169  switch ( $tag ) {
170  case 'Compression':
171  switch ( $val ) {
172  case 1:
173  case 2:
174  case 3:
175  case 4:
176  case 5:
177  case 6:
178  case 7:
179  case 8:
180  case 32773:
181  case 32946:
182  case 34712:
183  $val = $this->exifMsg( $tag, $val );
184  break;
185  default:
186  /* If not recognized, display as is. */
187  break;
188  }
189  break;
190 
191  case 'PhotometricInterpretation':
192  switch ( $val ) {
193  case 0:
194  case 1:
195  case 2:
196  case 3:
197  case 4:
198  case 5:
199  case 6:
200  case 8:
201  case 9:
202  case 10:
203  case 32803:
204  case 34892:
205  $val = $this->exifMsg( $tag, $val );
206  break;
207  default:
208  /* If not recognized, display as is. */
209  break;
210  }
211  break;
212 
213  case 'Orientation':
214  switch ( $val ) {
215  case 1:
216  case 2:
217  case 3:
218  case 4:
219  case 5:
220  case 6:
221  case 7:
222  case 8:
223  $val = $this->exifMsg( $tag, $val );
224  break;
225  default:
226  /* If not recognized, display as is. */
227  break;
228  }
229  break;
230 
231  case 'PlanarConfiguration':
232  switch ( $val ) {
233  case 1:
234  case 2:
235  $val = $this->exifMsg( $tag, $val );
236  break;
237  default:
238  /* If not recognized, display as is. */
239  break;
240  }
241  break;
242 
243  // TODO: YCbCrSubSampling
244  case 'YCbCrPositioning':
245  switch ( $val ) {
246  case 1:
247  case 2:
248  $val = $this->exifMsg( $tag, $val );
249  break;
250  default:
251  /* If not recognized, display as is. */
252  break;
253  }
254  break;
255 
256  case 'XResolution':
257  case 'YResolution':
258  switch ( $resolutionunit ) {
259  case 2:
260  $val = $this->exifMsg( 'XYResolution', 'i', $this->formatNum( $val ) );
261  break;
262  case 3:
263  $val = $this->exifMsg( 'XYResolution', 'c', $this->formatNum( $val ) );
264  break;
265  default:
266  /* If not recognized, display as is. */
267  break;
268  }
269  break;
270 
271  // TODO: YCbCrCoefficients #p27 (see annex E)
272  case 'ExifVersion':
273  // PHP likes to be the odd one out with casing of FlashPixVersion;
274  // https://www.exif.org/Exif2-2.PDF#page=32 and
275  // https://www.digitalgalen.net/Documents/External/XMP/XMPSpecificationPart2.pdf#page=51
276  // both use FlashpixVersion. However, since at least 2002, PHP has used FlashPixVersion at
277  // https://github.com/php/php-src/blame/master/ext/exif/exif.c#L725
278  case 'FlashPixVersion':
279  $val = (int)$val / 100;
280  break;
281 
282  case 'ColorSpace':
283  switch ( $val ) {
284  case 1:
285  case 65535:
286  $val = $this->exifMsg( $tag, $val );
287  break;
288  default:
289  /* If not recognized, display as is. */
290  break;
291  }
292  break;
293 
294  case 'ComponentsConfiguration':
295  switch ( $val ) {
296  case 0:
297  case 1:
298  case 2:
299  case 3:
300  case 4:
301  case 5:
302  case 6:
303  $val = $this->exifMsg( $tag, $val );
304  break;
305  default:
306  /* If not recognized, display as is. */
307  break;
308  }
309  break;
310 
311  case 'DateTime':
312  case 'DateTimeOriginal':
313  case 'DateTimeDigitized':
314  case 'DateTimeReleased':
315  case 'DateTimeExpires':
316  case 'GPSDateStamp':
317  case 'dc-date':
318  case 'DateTimeMetadata':
319  if ( $val == '0000:00:00 00:00:00' || $val == ' : : : : ' ) {
320  $val = $this->msg( 'exif-unknowndate' )->text();
321  } elseif ( preg_match(
322  '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/D',
323  $val
324  ) ) {
325  // Full date.
326  $time = wfTimestamp( TS_MW, $val );
327  if ( $time && intval( $time ) > 0 ) {
328  $val = $this->getLanguage()->timeanddate( $time );
329  }
330  } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d)$/D', $val ) ) {
331  // No second field. Still format the same
332  // since timeanddate doesn't include seconds anyways,
333  // but second still available in api
334  $time = wfTimestamp( TS_MW, $val . ':00' );
335  if ( $time && intval( $time ) > 0 ) {
336  $val = $this->getLanguage()->timeanddate( $time );
337  }
338  } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d)$/D', $val ) ) {
339  // If only the date but not the time is filled in.
340  $time = wfTimestamp( TS_MW, substr( $val, 0, 4 )
341  . substr( $val, 5, 2 )
342  . substr( $val, 8, 2 )
343  . '000000' );
344  if ( $time && intval( $time ) > 0 ) {
345  $val = $this->getLanguage()->date( $time );
346  }
347  }
348  // else it will just output $val without formatting it.
349  break;
350 
351  case 'ExposureProgram':
352  switch ( $val ) {
353  case 0:
354  case 1:
355  case 2:
356  case 3:
357  case 4:
358  case 5:
359  case 6:
360  case 7:
361  case 8:
362  $val = $this->exifMsg( $tag, $val );
363  break;
364  default:
365  /* If not recognized, display as is. */
366  break;
367  }
368  break;
369 
370  case 'SubjectDistance':
371  $val = $this->exifMsg( $tag, '', $this->formatNum( $val ) );
372  break;
373 
374  case 'MeteringMode':
375  switch ( $val ) {
376  case 0:
377  case 1:
378  case 2:
379  case 3:
380  case 4:
381  case 5:
382  case 6:
383  case 7:
384  case 255:
385  $val = $this->exifMsg( $tag, $val );
386  break;
387  default:
388  /* If not recognized, display as is. */
389  break;
390  }
391  break;
392 
393  case 'LightSource':
394  switch ( $val ) {
395  case 0:
396  case 1:
397  case 2:
398  case 3:
399  case 4:
400  case 9:
401  case 10:
402  case 11:
403  case 12:
404  case 13:
405  case 14:
406  case 15:
407  case 17:
408  case 18:
409  case 19:
410  case 20:
411  case 21:
412  case 22:
413  case 23:
414  case 24:
415  case 255:
416  $val = $this->exifMsg( $tag, $val );
417  break;
418  default:
419  /* If not recognized, display as is. */
420  break;
421  }
422  break;
423 
424  case 'Flash':
425  $flashDecode = [
426  'fired' => $val & 0b00000001,
427  'return' => ( $val & 0b00000110 ) >> 1,
428  'mode' => ( $val & 0b00011000 ) >> 3,
429  'function' => ( $val & 0b00100000 ) >> 5,
430  'redeye' => ( $val & 0b01000000 ) >> 6,
431  // 'reserved' => ( $val & 0b10000000 ) >> 7,
432  ];
433  $flashMsgs = [];
434  # We do not need to handle unknown values since all are used.
435  foreach ( $flashDecode as $subTag => $subValue ) {
436  # We do not need any message for zeroed values.
437  if ( $subTag != 'fired' && $subValue == 0 ) {
438  continue;
439  }
440  $fullTag = $tag . '-' . $subTag;
441  $flashMsgs[] = $this->exifMsg( $fullTag, $subValue );
442  }
443  $val = $this->getLanguage()->commaList( $flashMsgs );
444  break;
445 
446  case 'FocalPlaneResolutionUnit':
447  switch ( $val ) {
448  case 2:
449  $val = $this->exifMsg( $tag, $val );
450  break;
451  default:
452  /* If not recognized, display as is. */
453  break;
454  }
455  break;
456 
457  case 'SensingMethod':
458  switch ( $val ) {
459  case 1:
460  case 2:
461  case 3:
462  case 4:
463  case 5:
464  case 7:
465  case 8:
466  $val = $this->exifMsg( $tag, $val );
467  break;
468  default:
469  /* If not recognized, display as is. */
470  break;
471  }
472  break;
473 
474  case 'FileSource':
475  switch ( $val ) {
476  case 3:
477  $val = $this->exifMsg( $tag, $val );
478  break;
479  default:
480  /* If not recognized, display as is. */
481  break;
482  }
483  break;
484 
485  case 'SceneType':
486  switch ( $val ) {
487  case 1:
488  $val = $this->exifMsg( $tag, $val );
489  break;
490  default:
491  /* If not recognized, display as is. */
492  break;
493  }
494  break;
495 
496  case 'CustomRendered':
497  switch ( $val ) {
498  case 0:
499  case 1:
500  $val = $this->exifMsg( $tag, $val );
501  break;
502  default:
503  /* If not recognized, display as is. */
504  break;
505  }
506  break;
507 
508  case 'ExposureMode':
509  switch ( $val ) {
510  case 0:
511  case 1:
512  case 2:
513  $val = $this->exifMsg( $tag, $val );
514  break;
515  default:
516  /* If not recognized, display as is. */
517  break;
518  }
519  break;
520 
521  case 'WhiteBalance':
522  switch ( $val ) {
523  case 0:
524  case 1:
525  $val = $this->exifMsg( $tag, $val );
526  break;
527  default:
528  /* If not recognized, display as is. */
529  break;
530  }
531  break;
532 
533  case 'SceneCaptureType':
534  switch ( $val ) {
535  case 0:
536  case 1:
537  case 2:
538  case 3:
539  $val = $this->exifMsg( $tag, $val );
540  break;
541  default:
542  /* If not recognized, display as is. */
543  break;
544  }
545  break;
546 
547  case 'GainControl':
548  switch ( $val ) {
549  case 0:
550  case 1:
551  case 2:
552  case 3:
553  case 4:
554  $val = $this->exifMsg( $tag, $val );
555  break;
556  default:
557  /* If not recognized, display as is. */
558  break;
559  }
560  break;
561 
562  case 'Contrast':
563  switch ( $val ) {
564  case 0:
565  case 1:
566  case 2:
567  $val = $this->exifMsg( $tag, $val );
568  break;
569  default:
570  /* If not recognized, display as is. */
571  break;
572  }
573  break;
574 
575  case 'Saturation':
576  switch ( $val ) {
577  case 0:
578  case 1:
579  case 2:
580  $val = $this->exifMsg( $tag, $val );
581  break;
582  default:
583  /* If not recognized, display as is. */
584  break;
585  }
586  break;
587 
588  case 'Sharpness':
589  switch ( $val ) {
590  case 0:
591  case 1:
592  case 2:
593  $val = $this->exifMsg( $tag, $val );
594  break;
595  default:
596  /* If not recognized, display as is. */
597  break;
598  }
599  break;
600 
601  case 'SubjectDistanceRange':
602  switch ( $val ) {
603  case 0:
604  case 1:
605  case 2:
606  case 3:
607  $val = $this->exifMsg( $tag, $val );
608  break;
609  default:
610  /* If not recognized, display as is. */
611  break;
612  }
613  break;
614 
615  // The GPS...Ref values are kept for compatibility, probably won't be reached.
616  case 'GPSLatitudeRef':
617  case 'GPSDestLatitudeRef':
618  switch ( $val ) {
619  case 'N':
620  case 'S':
621  $val = $this->exifMsg( 'GPSLatitude', $val );
622  break;
623  default:
624  /* If not recognized, display as is. */
625  break;
626  }
627  break;
628 
629  case 'GPSLongitudeRef':
630  case 'GPSDestLongitudeRef':
631  switch ( $val ) {
632  case 'E':
633  case 'W':
634  $val = $this->exifMsg( 'GPSLongitude', $val );
635  break;
636  default:
637  /* If not recognized, display as is. */
638  break;
639  }
640  break;
641 
642  case 'GPSAltitude':
643  if ( $val < 0 ) {
644  $val = $this->exifMsg( 'GPSAltitude', 'below-sealevel', $this->formatNum( -$val, 3 ) );
645  } else {
646  $val = $this->exifMsg( 'GPSAltitude', 'above-sealevel', $this->formatNum( $val, 3 ) );
647  }
648  break;
649 
650  case 'GPSStatus':
651  switch ( $val ) {
652  case 'A':
653  case 'V':
654  $val = $this->exifMsg( $tag, $val );
655  break;
656  default:
657  /* If not recognized, display as is. */
658  break;
659  }
660  break;
661 
662  case 'GPSMeasureMode':
663  switch ( $val ) {
664  case 2:
665  case 3:
666  $val = $this->exifMsg( $tag, $val );
667  break;
668  default:
669  /* If not recognized, display as is. */
670  break;
671  }
672  break;
673 
674  case 'GPSTrackRef':
675  case 'GPSImgDirectionRef':
676  case 'GPSDestBearingRef':
677  switch ( $val ) {
678  case 'T':
679  case 'M':
680  $val = $this->exifMsg( 'GPSDirection', $val );
681  break;
682  default:
683  /* If not recognized, display as is. */
684  break;
685  }
686  break;
687 
688  case 'GPSLatitude':
689  case 'GPSDestLatitude':
690  $val = $this->formatCoords( $val, 'latitude' );
691  break;
692  case 'GPSLongitude':
693  case 'GPSDestLongitude':
694  $val = $this->formatCoords( $val, 'longitude' );
695  break;
696 
697  case 'GPSSpeedRef':
698  switch ( $val ) {
699  case 'K':
700  case 'M':
701  case 'N':
702  $val = $this->exifMsg( 'GPSSpeed', $val );
703  break;
704  default:
705  /* If not recognized, display as is. */
706  break;
707  }
708  break;
709 
710  case 'GPSDestDistanceRef':
711  switch ( $val ) {
712  case 'K':
713  case 'M':
714  case 'N':
715  $val = $this->exifMsg( 'GPSDestDistance', $val );
716  break;
717  default:
718  /* If not recognized, display as is. */
719  break;
720  }
721  break;
722 
723  case 'GPSDOP':
724  // See https://en.wikipedia.org/wiki/Dilution_of_precision_(GPS)
725  if ( $val <= 2 ) {
726  $val = $this->exifMsg( $tag, 'excellent', $this->formatNum( $val ) );
727  } elseif ( $val <= 5 ) {
728  $val = $this->exifMsg( $tag, 'good', $this->formatNum( $val ) );
729  } elseif ( $val <= 10 ) {
730  $val = $this->exifMsg( $tag, 'moderate', $this->formatNum( $val ) );
731  } elseif ( $val <= 20 ) {
732  $val = $this->exifMsg( $tag, 'fair', $this->formatNum( $val ) );
733  } else {
734  $val = $this->exifMsg( $tag, 'poor', $this->formatNum( $val ) );
735  }
736  break;
737 
738  // This is not in the Exif standard, just a special
739  // case for our purposes which enables wikis to wikify
740  // the make, model and software name to link to their articles.
741  case 'Make':
742  case 'Model':
743  $val = $this->exifMsg( $tag, '', $val );
744  break;
745 
746  case 'Software':
747  if ( is_array( $val ) ) {
748  if ( count( $val ) > 1 ) {
749  // if its a software, version array.
750  $val = $this->msg( 'exif-software-version-value', $val[0], $val[1] )->text();
751  } else {
752  // https://phabricator.wikimedia.org/T178130
753  $val = $this->exifMsg( $tag, '', $val[0] );
754  }
755  } else {
756  $val = $this->exifMsg( $tag, '', $val );
757  }
758  break;
759 
760  case 'ExposureTime':
761  // Show the pretty fraction as well as decimal version
762  $val = $this->msg( 'exif-exposuretime-format',
763  $this->formatFraction( $val ), $this->formatNum( $val ) )->text();
764  break;
765  case 'ISOSpeedRatings':
766  // If its = 65535 that means its at the
767  // limit of the size of Exif::short and
768  // is really higher.
769  if ( $val == '65535' ) {
770  $val = $this->exifMsg( $tag, 'overflow' );
771  } else {
772  $val = $this->formatNum( $val );
773  }
774  break;
775  case 'FNumber':
776  $val = $this->msg( 'exif-fnumber-format',
777  $this->formatNum( $val ) )->text();
778  break;
779 
780  case 'FocalLength':
781  case 'FocalLengthIn35mmFilm':
782  $val = $this->msg( 'exif-focallength-format',
783  $this->formatNum( $val ) )->text();
784  break;
785 
786  case 'MaxApertureValue':
787  if ( strpos( $val, '/' ) !== false ) {
788  // need to expand this earlier to calculate fNumber
789  list( $n, $d ) = explode( '/', $val );
790  if ( is_numeric( $n ) && is_numeric( $d ) ) {
791  $val = $n / $d;
792  }
793  }
794  if ( is_numeric( $val ) ) {
795  $fNumber = pow( 2, $val / 2 );
796  if ( $fNumber !== false ) {
797  $val = $this->msg( 'exif-maxaperturevalue-value',
798  $this->formatNum( $val ),
799  $this->formatNum( $fNumber, 2 )
800  )->text();
801  }
802  }
803  break;
804 
805  case 'iimCategory':
806  switch ( strtolower( $val ) ) {
807  // See pg 29 of IPTC photo
808  // metadata standard.
809  case 'ace':
810  case 'clj':
811  case 'dis':
812  case 'fin':
813  case 'edu':
814  case 'evn':
815  case 'hth':
816  case 'hum':
817  case 'lab':
818  case 'lif':
819  case 'pol':
820  case 'rel':
821  case 'sci':
822  case 'soi':
823  case 'spo':
824  case 'war':
825  case 'wea':
826  $val = $this->exifMsg(
827  'iimcategory',
828  $val
829  );
830  }
831  break;
832  case 'SubjectNewsCode':
833  // Essentially like iimCategory.
834  // 8 (numeric) digit hierarchical
835  // classification. We decode the
836  // first 2 digits, which provide
837  // a broad category.
838  $val = $this->convertNewsCode( $val );
839  break;
840  case 'Urgency':
841  // 1-8 with 1 being highest, 5 normal
842  // 0 is reserved, and 9 is 'user-defined'.
843  $urgency = '';
844  if ( $val == 0 || $val == 9 ) {
845  $urgency = 'other';
846  } elseif ( $val < 5 && $val > 1 ) {
847  $urgency = 'high';
848  } elseif ( $val == 5 ) {
849  $urgency = 'normal';
850  } elseif ( $val <= 8 && $val > 5 ) {
851  $urgency = 'low';
852  }
853 
854  if ( $urgency !== '' ) {
855  $val = $this->exifMsg( 'urgency',
856  $urgency, $val
857  );
858  }
859  break;
860 
861  // Things that have a unit of pixels.
862  case 'OriginalImageHeight':
863  case 'OriginalImageWidth':
864  case 'PixelXDimension':
865  case 'PixelYDimension':
866  case 'ImageWidth':
867  case 'ImageLength':
868  $val = $this->formatNum( $val ) . ' ' . $this->msg( 'unit-pixel' )->text();
869  break;
870 
871  // Do not transform fields with pure text.
872  // For some languages the formatNum()
873  // conversion results to wrong output like
874  // foo,bar@example,com or foo٫bar@example٫com.
875  // Also some 'numeric' things like Scene codes
876  // are included here as we really don't want
877  // commas inserted.
878  case 'ImageDescription':
879  case 'UserComment':
880  case 'Artist':
881  case 'Copyright':
882  case 'RelatedSoundFile':
883  case 'ImageUniqueID':
884  case 'SpectralSensitivity':
885  case 'GPSSatellites':
886  case 'GPSVersionID':
887  case 'GPSMapDatum':
888  case 'Keywords':
889  case 'WorldRegionDest':
890  case 'CountryDest':
891  case 'CountryCodeDest':
892  case 'ProvinceOrStateDest':
893  case 'CityDest':
894  case 'SublocationDest':
895  case 'WorldRegionCreated':
896  case 'CountryCreated':
897  case 'CountryCodeCreated':
898  case 'ProvinceOrStateCreated':
899  case 'CityCreated':
900  case 'SublocationCreated':
901  case 'ObjectName':
902  case 'SpecialInstructions':
903  case 'Headline':
904  case 'Credit':
905  case 'Source':
906  case 'EditStatus':
907  case 'FixtureIdentifier':
908  case 'LocationDest':
909  case 'LocationDestCode':
910  case 'Writer':
911  case 'JPEGFileComment':
912  case 'iimSupplementalCategory':
913  case 'OriginalTransmissionRef':
914  case 'Identifier':
915  case 'dc-contributor':
916  case 'dc-coverage':
917  case 'dc-publisher':
918  case 'dc-relation':
919  case 'dc-rights':
920  case 'dc-source':
921  case 'dc-type':
922  case 'Lens':
923  case 'SerialNumber':
924  case 'CameraOwnerName':
925  case 'Label':
926  case 'Nickname':
927  case 'RightsCertificate':
928  case 'CopyrightOwner':
929  case 'UsageTerms':
930  case 'WebStatement':
931  case 'OriginalDocumentID':
932  case 'LicenseUrl':
933  case 'MorePermissionsUrl':
934  case 'AttributionUrl':
935  case 'PreferredAttributionName':
936  case 'PNGFileComment':
937  case 'Disclaimer':
938  case 'ContentWarning':
939  case 'GIFFileComment':
940  case 'SceneCode':
941  case 'IntellectualGenre':
942  case 'Event':
943  case 'OrginisationInImage':
944  case 'PersonInImage':
945 
946  $val = htmlspecialchars( $val );
947  break;
948 
949  case 'ObjectCycle':
950  switch ( $val ) {
951  case 'a':
952  case 'p':
953  case 'b':
954  $val = $this->exifMsg( $tag, $val );
955  break;
956  default:
957  $val = htmlspecialchars( $val );
958  break;
959  }
960  break;
961  case 'Copyrighted':
962  switch ( $val ) {
963  case 'True':
964  case 'False':
965  $val = $this->exifMsg( $tag, $val );
966  break;
967  }
968  break;
969  case 'Rating':
970  if ( $val == '-1' ) {
971  $val = $this->exifMsg( $tag, 'rejected' );
972  } else {
973  $val = $this->formatNum( $val );
974  }
975  break;
976 
977  case 'LanguageCode':
978  $lang = Language::fetchLanguageName( strtolower( $val ), $this->getLanguage()->getCode() );
979  if ( $lang ) {
980  $val = htmlspecialchars( $lang );
981  } else {
982  $val = htmlspecialchars( $val );
983  }
984  break;
985 
986  default:
987  $val = $this->formatNum( $val );
988  break;
989  }
990  }
991  // End formatting values, start flattening arrays.
992  $vals = $this->flattenArrayReal( $vals, $type );
993  }
994 
995  return $tags;
996  }
997 
1012  public static function flattenArrayContentLang( $vals, $type = 'ul',
1013  $noHtml = false, $context = false
1014  ) {
1016  $obj = new FormatMetadata;
1017  if ( $context ) {
1018  $obj->setContext( $context );
1019  }
1020  $context = new DerivativeContext( $obj->getContext() );
1021  $context->setLanguage( $wgContLang );
1022  $obj->setContext( $context );
1023 
1024  return $obj->flattenArrayReal( $vals, $type, $noHtml );
1025  }
1026 
1043  public function flattenArrayReal( $vals, $type = 'ul', $noHtml = false ) {
1044  if ( !is_array( $vals ) ) {
1045  return $vals; // do nothing if not an array;
1046  }
1047 
1048  if ( isset( $vals['_type'] ) ) {
1049  $type = $vals['_type'];
1050  unset( $vals['_type'] );
1051  }
1052 
1053  if ( !is_array( $vals ) ) {
1054  return $vals; // do nothing if not an array;
1055  } elseif ( count( $vals ) === 1 && $type !== 'lang' && isset( $vals[0] ) ) {
1056  return $vals[0];
1057  } elseif ( count( $vals ) === 0 ) {
1058  wfDebug( __METHOD__ . " metadata array with 0 elements!\n" );
1059 
1060  return ""; // paranoia. This should never happen
1061  } else {
1062  /* @todo FIXME: This should hide some of the list entries if there are
1063  * say more than four. Especially if a field is translated into 20
1064  * languages, we don't want to show them all by default
1065  */
1066  switch ( $type ) {
1067  case 'lang':
1068  // Display default, followed by ContLang,
1069  // followed by the rest in no particular
1070  // order.
1071 
1072  // Todo: hide some items if really long list.
1073 
1074  $content = '';
1075 
1076  $priorityLanguages = $this->getPriorityLanguages();
1077  $defaultItem = false;
1078  $defaultLang = false;
1079 
1080  // If default is set, save it for later,
1081  // as we don't know if it's equal to
1082  // one of the lang codes. (In xmp
1083  // you specify the language for a
1084  // default property by having both
1085  // a default prop, and one in the language
1086  // that are identical)
1087  if ( isset( $vals['x-default'] ) ) {
1088  $defaultItem = $vals['x-default'];
1089  unset( $vals['x-default'] );
1090  }
1091  foreach ( $priorityLanguages as $pLang ) {
1092  if ( isset( $vals[$pLang] ) ) {
1093  $isDefault = false;
1094  if ( $vals[$pLang] === $defaultItem ) {
1095  $defaultItem = false;
1096  $isDefault = true;
1097  }
1098  $content .= $this->langItem(
1099  $vals[$pLang], $pLang,
1100  $isDefault, $noHtml );
1101 
1102  unset( $vals[$pLang] );
1103 
1104  if ( $this->singleLang ) {
1105  return Html::rawElement( 'span',
1106  [ 'lang' => $pLang ], $vals[$pLang] );
1107  }
1108  }
1109  }
1110 
1111  // Now do the rest.
1112  foreach ( $vals as $lang => $item ) {
1113  if ( $item === $defaultItem ) {
1114  $defaultLang = $lang;
1115  continue;
1116  }
1117  $content .= $this->langItem( $item,
1118  $lang, false, $noHtml );
1119  if ( $this->singleLang ) {
1120  return Html::rawElement( 'span',
1121  [ 'lang' => $lang ], $item );
1122  }
1123  }
1124  if ( $defaultItem !== false ) {
1125  $content = $this->langItem( $defaultItem,
1126  $defaultLang, true, $noHtml ) .
1127  $content;
1128  if ( $this->singleLang ) {
1129  return $defaultItem;
1130  }
1131  }
1132  if ( $noHtml ) {
1133  return $content;
1134  }
1135 
1136  return '<ul class="metadata-langlist">' .
1137  $content .
1138  '</ul>';
1139  case 'ol':
1140  if ( $noHtml ) {
1141  return "\n#" . implode( "\n#", $vals );
1142  }
1143 
1144  return "<ol><li>" . implode( "</li>\n<li>", $vals ) . '</li></ol>';
1145  case 'ul':
1146  default:
1147  if ( $noHtml ) {
1148  return "\n*" . implode( "\n*", $vals );
1149  }
1150 
1151  return "<ul><li>" . implode( "</li>\n<li>", $vals ) . '</li></ul>';
1152  }
1153  }
1154  }
1155 
1166  private function langItem( $value, $lang, $default = false, $noHtml = false ) {
1167  if ( $lang === false && $default === false ) {
1168  throw new MWException( '$lang and $default cannot both '
1169  . 'be false.' );
1170  }
1171 
1172  if ( $noHtml ) {
1173  $wrappedValue = $value;
1174  } else {
1175  $wrappedValue = '<span class="mw-metadata-lang-value">'
1176  . $value . '</span>';
1177  }
1178 
1179  if ( $lang === false ) {
1180  $msg = $this->msg( 'metadata-langitem-default', $wrappedValue );
1181  if ( $noHtml ) {
1182  return $msg->text() . "\n\n";
1183  } /* else */
1184 
1185  return '<li class="mw-metadata-lang-default">'
1186  . $msg->text()
1187  . "</li>\n";
1188  }
1189 
1190  $lowLang = strtolower( $lang );
1191  $langName = Language::fetchLanguageName( $lowLang );
1192  if ( $langName === '' ) {
1193  // try just the base language name. (aka en-US -> en ).
1194  list( $langPrefix ) = explode( '-', $lowLang, 2 );
1195  $langName = Language::fetchLanguageName( $langPrefix );
1196  if ( $langName === '' ) {
1197  // give up.
1198  $langName = $lang;
1199  }
1200  }
1201  // else we have a language specified
1202 
1203  $msg = $this->msg( 'metadata-langitem', $wrappedValue, $langName, $lang );
1204  if ( $noHtml ) {
1205  return '*' . $msg->text();
1206  } /* else: */
1207 
1208  $item = '<li class="mw-metadata-lang-code-'
1209  . $lang;
1210  if ( $default ) {
1211  $item .= ' mw-metadata-lang-default';
1212  }
1213  $item .= '" lang="' . $lang . '">';
1214  $item .= $msg->text();
1215  $item .= "</li>\n";
1216 
1217  return $item;
1218  }
1219 
1229  private function exifMsg( $tag, $val, $arg = null, $arg2 = null ) {
1231 
1232  if ( $val === '' ) {
1233  $val = 'value';
1234  }
1235 
1236  return $this->msg( $wgContLang->lc( "exif-$tag-$val" ), $arg, $arg2 )->text();
1237  }
1238 
1247  private function formatNum( $num, $round = false ) {
1248  $m = [];
1249  if ( is_array( $num ) ) {
1250  $out = [];
1251  foreach ( $num as $number ) {
1252  $out[] = $this->formatNum( $number );
1253  }
1254 
1255  return $this->getLanguage()->commaList( $out );
1256  }
1257  if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
1258  if ( $m[2] != 0 ) {
1259  $newNum = $m[1] / $m[2];
1260  if ( $round !== false ) {
1261  $newNum = round( $newNum, $round );
1262  }
1263  } else {
1264  $newNum = $num;
1265  }
1266 
1267  return $this->getLanguage()->formatNum( $newNum );
1268  } else {
1269  if ( is_numeric( $num ) && $round !== false ) {
1270  $num = round( $num, $round );
1271  }
1272 
1273  return $this->getLanguage()->formatNum( $num );
1274  }
1275  }
1276 
1283  private function formatFraction( $num ) {
1284  $m = [];
1285  if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
1286  $numerator = intval( $m[1] );
1287  $denominator = intval( $m[2] );
1288  $gcd = $this->gcd( abs( $numerator ), $denominator );
1289  if ( $gcd != 0 ) {
1290  // 0 shouldn't happen! ;)
1291  return $this->formatNum( $numerator / $gcd ) . '/' . $this->formatNum( $denominator / $gcd );
1292  }
1293  }
1294 
1295  return $this->formatNum( $num );
1296  }
1297 
1305  private function gcd( $a, $b ) {
1306  /*
1307  // https://en.wikipedia.org/wiki/Euclidean_algorithm
1308  // Recursive form would be:
1309  if( $b == 0 )
1310  return $a;
1311  else
1312  return gcd( $b, $a % $b );
1313  */
1314  while ( $b != 0 ) {
1315  $remainder = $a % $b;
1316 
1317  // tail recursion...
1318  $a = $b;
1319  $b = $remainder;
1320  }
1321 
1322  return $a;
1323  }
1324 
1337  private function convertNewsCode( $val ) {
1338  if ( !preg_match( '/^\d{8}$/D', $val ) ) {
1339  // Not a valid news code.
1340  return $val;
1341  }
1342  $cat = '';
1343  switch ( substr( $val, 0, 2 ) ) {
1344  case '01':
1345  $cat = 'ace';
1346  break;
1347  case '02':
1348  $cat = 'clj';
1349  break;
1350  case '03':
1351  $cat = 'dis';
1352  break;
1353  case '04':
1354  $cat = 'fin';
1355  break;
1356  case '05':
1357  $cat = 'edu';
1358  break;
1359  case '06':
1360  $cat = 'evn';
1361  break;
1362  case '07':
1363  $cat = 'hth';
1364  break;
1365  case '08':
1366  $cat = 'hum';
1367  break;
1368  case '09':
1369  $cat = 'lab';
1370  break;
1371  case '10':
1372  $cat = 'lif';
1373  break;
1374  case '11':
1375  $cat = 'pol';
1376  break;
1377  case '12':
1378  $cat = 'rel';
1379  break;
1380  case '13':
1381  $cat = 'sci';
1382  break;
1383  case '14':
1384  $cat = 'soi';
1385  break;
1386  case '15':
1387  $cat = 'spo';
1388  break;
1389  case '16':
1390  $cat = 'war';
1391  break;
1392  case '17':
1393  $cat = 'wea';
1394  break;
1395  }
1396  if ( $cat !== '' ) {
1397  $catMsg = $this->exifMsg( 'iimcategory', $cat );
1398  $val = $this->exifMsg( 'subjectnewscode', '', $val, $catMsg );
1399  }
1400 
1401  return $val;
1402  }
1403 
1412  private function formatCoords( $coord, $type ) {
1413  if ( !is_numeric( $coord ) ) {
1414  wfDebugLog( 'exif', __METHOD__ . ": \"$coord\" is not a number" );
1415  return (string)$coord;
1416  }
1417 
1418  $ref = '';
1419  if ( $coord < 0 ) {
1420  $nCoord = -$coord;
1421  if ( $type === 'latitude' ) {
1422  $ref = 'S';
1423  } elseif ( $type === 'longitude' ) {
1424  $ref = 'W';
1425  }
1426  } else {
1427  $nCoord = (float)$coord;
1428  if ( $type === 'latitude' ) {
1429  $ref = 'N';
1430  } elseif ( $type === 'longitude' ) {
1431  $ref = 'E';
1432  }
1433  }
1434 
1435  $deg = floor( $nCoord );
1436  $min = floor( ( $nCoord - $deg ) * 60 );
1437  $sec = round( ( ( $nCoord - $deg ) * 60 - $min ) * 60, 2 );
1438 
1439  $deg = $this->formatNum( $deg );
1440  $min = $this->formatNum( $min );
1441  $sec = $this->formatNum( $sec );
1442 
1443  // Note the default message "$1° $2′ $3″ $4" ignores the 5th parameter
1444  return $this->msg( 'exif-coordinate-format', $deg, $min, $sec, $ref, $coord )->text();
1445  }
1446 
1461  public function collapseContactInfo( $vals ) {
1462  if ( !( isset( $vals['CiAdrExtadr'] )
1463  || isset( $vals['CiAdrCity'] )
1464  || isset( $vals['CiAdrCtry'] )
1465  || isset( $vals['CiEmailWork'] )
1466  || isset( $vals['CiTelWork'] )
1467  || isset( $vals['CiAdrPcode'] )
1468  || isset( $vals['CiAdrRegion'] )
1469  || isset( $vals['CiUrlWork'] )
1470  ) ) {
1471  // We don't have any sub-properties
1472  // This could happen if its using old
1473  // iptc that just had this as a free-form
1474  // text value.
1475  // Note: We run this through htmlspecialchars
1476  // partially to be consistent, and partially
1477  // because people often insert >, etc into
1478  // the metadata which should not be interpreted
1479  // but we still want to auto-link urls.
1480  foreach ( $vals as &$val ) {
1481  $val = htmlspecialchars( $val );
1482  }
1483 
1484  return $this->flattenArrayReal( $vals );
1485  } else {
1486  // We have a real ContactInfo field.
1487  // Its unclear if all these fields have to be
1488  // set, so assume they do not.
1489  $url = $tel = $street = $city = $country = '';
1490  $email = $postal = $region = '';
1491 
1492  // Also note, some of the class names this uses
1493  // are similar to those used by hCard. This is
1494  // mostly because they're sensible names. This
1495  // does not (and does not attempt to) output
1496  // stuff in the hCard microformat. However it
1497  // might output in the adr microformat.
1498 
1499  if ( isset( $vals['CiAdrExtadr'] ) ) {
1500  // Todo: This can potentially be multi-line.
1501  // Need to check how that works in XMP.
1502  $street = '<span class="extended-address">'
1503  . htmlspecialchars(
1504  $vals['CiAdrExtadr'] )
1505  . '</span>';
1506  }
1507  if ( isset( $vals['CiAdrCity'] ) ) {
1508  $city = '<span class="locality">'
1509  . htmlspecialchars( $vals['CiAdrCity'] )
1510  . '</span>';
1511  }
1512  if ( isset( $vals['CiAdrCtry'] ) ) {
1513  $country = '<span class="country-name">'
1514  . htmlspecialchars( $vals['CiAdrCtry'] )
1515  . '</span>';
1516  }
1517  if ( isset( $vals['CiEmailWork'] ) ) {
1518  $emails = [];
1519  // Have to split multiple emails at commas/new lines.
1520  $splitEmails = explode( "\n", $vals['CiEmailWork'] );
1521  foreach ( $splitEmails as $e1 ) {
1522  // Also split on comma
1523  foreach ( explode( ',', $e1 ) as $e2 ) {
1524  $finalEmail = trim( $e2 );
1525  if ( $finalEmail == ',' || $finalEmail == '' ) {
1526  continue;
1527  }
1528  if ( strpos( $finalEmail, '<' ) !== false ) {
1529  // Don't do fancy formatting to
1530  // "My name" <foo@bar.com> style stuff
1531  $emails[] = $finalEmail;
1532  } else {
1533  $emails[] = '[mailto:'
1534  . $finalEmail
1535  . ' <span class="email">'
1536  . $finalEmail
1537  . '</span>]';
1538  }
1539  }
1540  }
1541  $email = implode( ', ', $emails );
1542  }
1543  if ( isset( $vals['CiTelWork'] ) ) {
1544  $tel = '<span class="tel">'
1545  . htmlspecialchars( $vals['CiTelWork'] )
1546  . '</span>';
1547  }
1548  if ( isset( $vals['CiAdrPcode'] ) ) {
1549  $postal = '<span class="postal-code">'
1550  . htmlspecialchars(
1551  $vals['CiAdrPcode'] )
1552  . '</span>';
1553  }
1554  if ( isset( $vals['CiAdrRegion'] ) ) {
1555  // Note this is province/state.
1556  $region = '<span class="region">'
1557  . htmlspecialchars(
1558  $vals['CiAdrRegion'] )
1559  . '</span>';
1560  }
1561  if ( isset( $vals['CiUrlWork'] ) ) {
1562  $url = '<span class="url">'
1563  . htmlspecialchars( $vals['CiUrlWork'] )
1564  . '</span>';
1565  }
1566 
1567  return $this->msg( 'exif-contact-value', $email, $url,
1568  $street, $city, $region, $postal, $country,
1569  $tel )->text();
1570  }
1571  }
1572 
1579  public static function getVisibleFields() {
1580  $fields = [];
1581  $lines = explode( "\n", wfMessage( 'metadata-fields' )->inContentLanguage()->text() );
1582  foreach ( $lines as $line ) {
1583  $matches = [];
1584  if ( preg_match( '/^\\*\s*(.*?)\s*$/', $line, $matches ) ) {
1585  $fields[] = $matches[1];
1586  }
1587  }
1588  $fields = array_map( 'strtolower', $fields );
1589 
1590  return $fields;
1591  }
1592 
1600  public function fetchExtendedMetadata( File $file ) {
1601  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1602 
1603  // If revision deleted, exit immediately
1604  if ( $file->isDeleted( File::DELETED_FILE ) ) {
1605  return [];
1606  }
1607 
1608  $cacheKey = $cache->makeKey(
1609  'getExtendedMetadata',
1610  $this->getLanguage()->getCode(),
1611  (int)$this->singleLang,
1612  $file->getSha1()
1613  );
1614 
1615  $cachedValue = $cache->get( $cacheKey );
1616  if (
1617  $cachedValue
1618  && Hooks::run( 'ValidateExtendedMetadataCache', [ $cachedValue['timestamp'], $file ] )
1619  ) {
1620  $extendedMetadata = $cachedValue['data'];
1621  } else {
1622  $maxCacheTime = ( $file instanceof ForeignAPIFile ) ? 60 * 60 * 12 : 60 * 60 * 24 * 30;
1623  $fileMetadata = $this->getExtendedMetadataFromFile( $file );
1624  $extendedMetadata = $this->getExtendedMetadataFromHook( $file, $fileMetadata, $maxCacheTime );
1625  if ( $this->singleLang ) {
1626  $this->resolveMultilangMetadata( $extendedMetadata );
1627  }
1628  $this->discardMultipleValues( $extendedMetadata );
1629  // Make sure the metadata won't break the API when an XML format is used.
1630  // This is an API-specific function so it would be cleaner to call it from
1631  // outside fetchExtendedMetadata, but this way we don't need to redo the
1632  // computation on a cache hit.
1633  $this->sanitizeArrayForAPI( $extendedMetadata );
1634  $valueToCache = [ 'data' => $extendedMetadata, 'timestamp' => wfTimestampNow() ];
1635  $cache->set( $cacheKey, $valueToCache, $maxCacheTime );
1636  }
1637 
1638  return $extendedMetadata;
1639  }
1640 
1650  protected function getExtendedMetadataFromFile( File $file ) {
1651  // If this is a remote file accessed via an API request, we already
1652  // have remote metadata so we just ignore any local one
1653  if ( $file instanceof ForeignAPIFile ) {
1654  // In case of error we pretend no metadata - this will get cached.
1655  // Might or might not be a good idea.
1656  return $file->getExtendedMetadata() ?: [];
1657  }
1658 
1659  $uploadDate = wfTimestamp( TS_ISO_8601, $file->getTimestamp() );
1660 
1661  $fileMetadata = [
1662  // This is modification time, which is close to "upload" time.
1663  'DateTime' => [
1664  'value' => $uploadDate,
1665  'source' => 'mediawiki-metadata',
1666  ],
1667  ];
1668 
1669  $title = $file->getTitle();
1670  if ( $title ) {
1671  $text = $title->getText();
1672  $pos = strrpos( $text, '.' );
1673 
1674  if ( $pos ) {
1675  $name = substr( $text, 0, $pos );
1676  } else {
1677  $name = $text;
1678  }
1679 
1680  $fileMetadata['ObjectName'] = [
1681  'value' => $name,
1682  'source' => 'mediawiki-metadata',
1683  ];
1684  }
1685 
1686  return $fileMetadata;
1687  }
1688 
1699  protected function getExtendedMetadataFromHook( File $file, array $extendedMetadata,
1700  &$maxCacheTime
1701  ) {
1702  Hooks::run( 'GetExtendedMetadata', [
1703  &$extendedMetadata,
1704  $file,
1705  $this->getContext(),
1706  $this->singleLang,
1707  &$maxCacheTime
1708  ] );
1709 
1710  $visible = array_flip( self::getVisibleFields() );
1711  foreach ( $extendedMetadata as $key => $value ) {
1712  if ( !isset( $visible[strtolower( $key )] ) ) {
1713  $extendedMetadata[$key]['hidden'] = '';
1714  }
1715  }
1716 
1717  return $extendedMetadata;
1718  }
1719 
1728  protected function resolveMultilangValue( $value ) {
1729  if (
1730  !is_array( $value )
1731  || !isset( $value['_type'] )
1732  || $value['_type'] != 'lang'
1733  ) {
1734  return $value; // do nothing if not a multilang array
1735  }
1736 
1737  // choose the language best matching user or site settings
1738  $priorityLanguages = $this->getPriorityLanguages();
1739  foreach ( $priorityLanguages as $lang ) {
1740  if ( isset( $value[$lang] ) ) {
1741  return $value[$lang];
1742  }
1743  }
1744 
1745  // otherwise go with the default language, if set
1746  if ( isset( $value['x-default'] ) ) {
1747  return $value['x-default'];
1748  }
1749 
1750  // otherwise just return any one language
1751  unset( $value['_type'] );
1752  if ( !empty( $value ) ) {
1753  return reset( $value );
1754  }
1755 
1756  // this should not happen; signal error
1757  return null;
1758  }
1759 
1769  protected function resolveMultivalueValue( $value ) {
1770  if ( !is_array( $value ) ) {
1771  return $value;
1772  } elseif ( isset( $value['_type'] ) && $value['_type'] === 'lang' ) {
1773  // if this is a multilang array, process fields separately
1774  $newValue = [];
1775  foreach ( $value as $k => $v ) {
1776  $newValue[$k] = $this->resolveMultivalueValue( $v );
1777  }
1778  return $newValue;
1779  } else { // _type is 'ul' or 'ol' or missing in which case it defaults to 'ul'
1780  $v = reset( $value );
1781  if ( key( $value ) === '_type' ) {
1782  $v = next( $value );
1783  }
1784  return $v;
1785  }
1786  }
1787 
1794  protected function resolveMultilangMetadata( &$metadata ) {
1795  if ( !is_array( $metadata ) ) {
1796  return;
1797  }
1798  foreach ( $metadata as &$field ) {
1799  if ( isset( $field['value'] ) ) {
1800  $field['value'] = $this->resolveMultilangValue( $field['value'] );
1801  }
1802  }
1803  }
1804 
1811  protected function discardMultipleValues( &$metadata ) {
1812  if ( !is_array( $metadata ) ) {
1813  return;
1814  }
1815  foreach ( $metadata as $key => &$field ) {
1816  if ( $key === 'Software' || $key === 'Contact' ) {
1817  // we skip some fields which have composite values. They are not particularly interesting
1818  // and you can get them via the metadata / commonmetadata APIs anyway.
1819  continue;
1820  }
1821  if ( isset( $field['value'] ) ) {
1822  $field['value'] = $this->resolveMultivalueValue( $field['value'] );
1823  }
1824  }
1825  }
1826 
1831  protected function sanitizeArrayForAPI( &$arr ) {
1832  if ( !is_array( $arr ) ) {
1833  return;
1834  }
1835 
1836  $counter = 1;
1837  foreach ( $arr as $key => &$value ) {
1838  $sanitizedKey = $this->sanitizeKeyForAPI( $key );
1839  if ( $sanitizedKey !== $key ) {
1840  if ( isset( $arr[$sanitizedKey] ) ) {
1841  // Make the sanitized keys hopefully unique.
1842  // To make it definitely unique would be too much effort, given that
1843  // sanitizing is only needed for misformatted metadata anyway, but
1844  // this at least covers the case when $arr is numeric.
1845  $sanitizedKey .= $counter;
1846  ++$counter;
1847  }
1848  $arr[$sanitizedKey] = $arr[$key];
1849  unset( $arr[$key] );
1850  }
1851  if ( is_array( $value ) ) {
1852  $this->sanitizeArrayForAPI( $value );
1853  }
1854  }
1855 
1856  // Handle API metadata keys (particularly "_type")
1857  $keys = array_filter( array_keys( $arr ), 'ApiResult::isMetadataKey' );
1858  if ( $keys ) {
1860  }
1861  }
1862 
1869  protected function sanitizeKeyForAPI( $key ) {
1870  // drop all characters which are not valid in an XML tag name
1871  // a bunch of non-ASCII letters would be valid but probably won't
1872  // be used so we take the easy way
1873  $key = preg_replace( '/[^a-zA-z0-9_:.\-]/', '', $key );
1874  // drop characters which are invalid at the first position
1875  $key = preg_replace( '/^[\d\-.]+/', '', $key );
1876 
1877  if ( $key == '' ) {
1878  $key = '_';
1879  }
1880 
1881  // special case for an internal keyword
1882  if ( $key == '_element' ) {
1883  $key = 'element';
1884  }
1885 
1886  return $key;
1887  }
1888 
1895  protected function getPriorityLanguages() {
1896  $priorityLanguages =
1898  $priorityLanguages = array_merge(
1899  (array)$this->getLanguage()->getCode(),
1900  $priorityLanguages[0],
1901  $priorityLanguages[1]
1902  );
1903 
1904  return $priorityLanguages;
1905  }
1906 }
$time
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1795
ContextSource\$context
IContextSource $context
Definition: ContextSource.php:33
ContextSource\getContext
getContext()
Get the base IContextSource object.
Definition: ContextSource.php:40
use
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Definition: APACHE-LICENSE-2.0.txt:10
FormatMetadata\sanitizeKeyForAPI
sanitizeKeyForAPI( $key)
Turns a string into a valid API identifier.
Definition: FormatMetadata.php:1869
FormatMetadata\gcd
gcd( $a, $b)
Calculate the greatest common divisor of two integers.
Definition: FormatMetadata.php:1305
FormatMetadata\getFormattedData
static getFormattedData( $tags, $context=false)
Numbers given by Exif user agents are often magical, that is they should be replaced by a detailed ex...
Definition: FormatMetadata.php:82
wfMessage
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt
array
the array() calling protocol came about after MediaWiki 1.4rc1.
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:33
text
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition: design.txt:18
ContextSource\msg
msg( $key)
Get a Message object with context set Parameters are the same as wfMessage()
Definition: ContextSource.php:168
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1980
FormatMetadata\formatNum
formatNum( $num, $round=false)
Format a number, convert numbers from fractions into floating point numbers, joins arrays of numbers ...
Definition: FormatMetadata.php:1247
FormatMetadata\fetchExtendedMetadata
fetchExtendedMetadata(File $file)
Get an array of extended metadata.
Definition: FormatMetadata.php:1600
File\getTimestamp
getTimestamp()
Get the 14-character timestamp of the file upload.
Definition: File.php:2115
File\getSha1
getSha1()
Get the SHA-1 base 36 hash of the file.
Definition: File.php:2137
$out
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition: hooks.txt:864
$s
$s
Definition: mergeMessageFileList.php:187
FormatMetadata\getExtendedMetadataFromFile
getExtendedMetadataFromFile(File $file)
Get file-based metadata in standardized format.
Definition: FormatMetadata.php:1650
File\isDeleted
isDeleted( $field)
Is this file a "deleted" file in a private archive? STUB.
Definition: File.php:1895
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:1087
FormatMetadata\formatFraction
formatFraction( $num)
Format a rational number, reducing fractions.
Definition: FormatMetadata.php:1283
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:37
FormatMetadata\setSingleLanguage
setSingleLanguage( $val)
Trigger only outputting single language for multilanguage fields.
Definition: FormatMetadata.php:65
ContextSource\getLanguage
getLanguage()
Definition: ContextSource.php:128
key
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add in any and then calling but I prefer the flexibility This should also do the output encoding The system allocates a global one in $wgOut Title Represents the title of an and does all the work of translating among various forms such as plain database key
Definition: design.txt:26
DerivativeContext
An IContextSource implementation which will inherit context from another source but allow individual ...
Definition: DerivativeContext.php:30
FormatMetadata\discardMultipleValues
discardMultipleValues(&$metadata)
Takes an array returned by the getExtendedMetadata* functions, and turns all fields into single-value...
Definition: FormatMetadata.php:1811
FormatMetadata\convertNewsCode
convertNewsCode( $val)
Fetch the human readable version of a news code.
Definition: FormatMetadata.php:1337
File
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition: File.php:51
MWException
MediaWiki exception.
Definition: MWException.php:26
FormatMetadata\resolveMultivalueValue
resolveMultivalueValue( $value)
Turns an XMP-style multivalue array into a single value by dropping all but the first value.
Definition: FormatMetadata.php:1769
FormatMetadata\formatCoords
formatCoords( $coord, $type)
Format a coordinate value, convert numbers from floating point into degree minute second representati...
Definition: FormatMetadata.php:1412
FormatMetadata\getPriorityLanguages
getPriorityLanguages()
Returns a list of languages (first is best) to use when formatting multilang fields,...
Definition: FormatMetadata.php:1895
FormatMetadata\langItem
langItem( $value, $lang, $default=false, $noHtml=false)
Helper function for creating lists of translations.
Definition: FormatMetadata.php:1166
$matches
$matches
Definition: NoLocalSettings.php:24
ContextSource
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
Definition: ContextSource.php:29
FormatMetadata\getExtendedMetadataFromHook
getExtendedMetadataFromHook(File $file, array $extendedMetadata, &$maxCacheTime)
Get additional metadata from hooks in standardized format.
Definition: FormatMetadata.php:1699
$lines
$lines
Definition: router.php:61
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:964
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:95
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:2009
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:994
ContextSource\setContext
setContext(IContextSource $context)
Definition: ContextSource.php:55
FormatMetadata\collapseContactInfo
collapseContactInfo( $vals)
Format the contact info field into a single value.
Definition: FormatMetadata.php:1461
list
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
$line
$line
Definition: cdb.php:59
FormatMetadata\$singleLang
bool $singleLang
Only output a single language for multi-language fields.
Definition: FormatMetadata.php:57
$value
$value
Definition: styleTest.css.php:45
Language\fetchLanguageName
static fetchLanguageName( $code, $inLanguage=null, $include='all')
Definition: Language.php:896
FormatMetadata\flattenArrayContentLang
static flattenArrayContentLang( $vals, $type='ul', $noHtml=false, $context=false)
Flatten an array, using the content language for any messages.
Definition: FormatMetadata.php:1012
FormatMetadata
Format Image metadata values into a human readable form.
Definition: FormatMetadata.php:51
FormatMetadata\exifMsg
exifMsg( $tag, $val, $arg=null, $arg2=null)
Convenience function for getFormattedData()
Definition: FormatMetadata.php:1229
FormatMetadata\sanitizeArrayForAPI
sanitizeArrayForAPI(&$arr)
Makes sure the given array is a valid API response fragment.
Definition: FormatMetadata.php:1831
File\getTitle
getTitle()
Return the associated title object.
Definition: File.php:326
ApiResult\setPreserveKeysList
static setPreserveKeysList(array &$arr, $names)
Preserve specified keys.
Definition: ApiResult.php:675
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
FormatMetadata\resolveMultilangMetadata
resolveMultilangMetadata(&$metadata)
Takes an array returned by the getExtendedMetadata* functions, and resolves multi-language values in ...
Definition: FormatMetadata.php:1794
$cache
$cache
Definition: mcc.php:33
Language\getFallbacksIncludingSiteLanguage
static getFallbacksIncludingSiteLanguage( $code)
Get the ordered list of fallback languages, ending with the fallback language chain for the site lang...
Definition: Language.php:4564
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:22
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:209
$keys
$keys
Definition: testCompression.php:67
FormatMetadata\flattenArrayReal
flattenArrayReal( $vals, $type='ul', $noHtml=false)
A function to collapse multivalued tags into a single value.
Definition: FormatMetadata.php:1043
File\DELETED_FILE
const DELETED_FILE
Definition: File.php:53
MediaWikiServices
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency MediaWikiServices
Definition: injection.txt:25
ForeignAPIFile
Foreign file accessible through api.php requests.
Definition: ForeignAPIFile.php:30
FormatMetadata\getVisibleFields
static getVisibleFields()
Get a list of fields that are visible by default.
Definition: FormatMetadata.php:1579
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:203
FormatMetadata\resolveMultilangValue
resolveMultilangValue( $value)
Turns an XMP-style multilang array into a single value.
Definition: FormatMetadata.php:1728
FormatMetadata\makeFormattedData
makeFormattedData( $tags)
Numbers given by Exif user agents are often magical, that is they should be replaced by a detailed ex...
Definition: FormatMetadata.php:102
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2171
$wgContLang
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the content language as $wgContLang
Definition: design.txt:57
$type
$type
Definition: testCompression.php:48