MediaWiki  1.31.0
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  case 'FlashpixVersion':
274  $val = "$val" / 100;
275  break;
276 
277  case 'ColorSpace':
278  switch ( $val ) {
279  case 1:
280  case 65535:
281  $val = $this->exifMsg( $tag, $val );
282  break;
283  default:
284  /* If not recognized, display as is. */
285  break;
286  }
287  break;
288 
289  case 'ComponentsConfiguration':
290  switch ( $val ) {
291  case 0:
292  case 1:
293  case 2:
294  case 3:
295  case 4:
296  case 5:
297  case 6:
298  $val = $this->exifMsg( $tag, $val );
299  break;
300  default:
301  /* If not recognized, display as is. */
302  break;
303  }
304  break;
305 
306  case 'DateTime':
307  case 'DateTimeOriginal':
308  case 'DateTimeDigitized':
309  case 'DateTimeReleased':
310  case 'DateTimeExpires':
311  case 'GPSDateStamp':
312  case 'dc-date':
313  case 'DateTimeMetadata':
314  if ( $val == '0000:00:00 00:00:00' || $val == ' : : : : ' ) {
315  $val = $this->msg( 'exif-unknowndate' )->text();
316  } elseif ( preg_match(
317  '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/D',
318  $val
319  ) ) {
320  // Full date.
321  $time = wfTimestamp( TS_MW, $val );
322  if ( $time && intval( $time ) > 0 ) {
323  $val = $this->getLanguage()->timeanddate( $time );
324  }
325  } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d)$/D', $val ) ) {
326  // No second field. Still format the same
327  // since timeanddate doesn't include seconds anyways,
328  // but second still available in api
329  $time = wfTimestamp( TS_MW, $val . ':00' );
330  if ( $time && intval( $time ) > 0 ) {
331  $val = $this->getLanguage()->timeanddate( $time );
332  }
333  } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d)$/D', $val ) ) {
334  // If only the date but not the time is filled in.
335  $time = wfTimestamp( TS_MW, substr( $val, 0, 4 )
336  . substr( $val, 5, 2 )
337  . substr( $val, 8, 2 )
338  . '000000' );
339  if ( $time && intval( $time ) > 0 ) {
340  $val = $this->getLanguage()->date( $time );
341  }
342  }
343  // else it will just output $val without formatting it.
344  break;
345 
346  case 'ExposureProgram':
347  switch ( $val ) {
348  case 0:
349  case 1:
350  case 2:
351  case 3:
352  case 4:
353  case 5:
354  case 6:
355  case 7:
356  case 8:
357  $val = $this->exifMsg( $tag, $val );
358  break;
359  default:
360  /* If not recognized, display as is. */
361  break;
362  }
363  break;
364 
365  case 'SubjectDistance':
366  $val = $this->exifMsg( $tag, '', $this->formatNum( $val ) );
367  break;
368 
369  case 'MeteringMode':
370  switch ( $val ) {
371  case 0:
372  case 1:
373  case 2:
374  case 3:
375  case 4:
376  case 5:
377  case 6:
378  case 7:
379  case 255:
380  $val = $this->exifMsg( $tag, $val );
381  break;
382  default:
383  /* If not recognized, display as is. */
384  break;
385  }
386  break;
387 
388  case 'LightSource':
389  switch ( $val ) {
390  case 0:
391  case 1:
392  case 2:
393  case 3:
394  case 4:
395  case 9:
396  case 10:
397  case 11:
398  case 12:
399  case 13:
400  case 14:
401  case 15:
402  case 17:
403  case 18:
404  case 19:
405  case 20:
406  case 21:
407  case 22:
408  case 23:
409  case 24:
410  case 255:
411  $val = $this->exifMsg( $tag, $val );
412  break;
413  default:
414  /* If not recognized, display as is. */
415  break;
416  }
417  break;
418 
419  case 'Flash':
420  $flashDecode = [
421  'fired' => $val & 0b00000001,
422  'return' => ( $val & 0b00000110 ) >> 1,
423  'mode' => ( $val & 0b00011000 ) >> 3,
424  'function' => ( $val & 0b00100000 ) >> 5,
425  'redeye' => ( $val & 0b01000000 ) >> 6,
426  // 'reserved' => ( $val & 0b10000000 ) >> 7,
427  ];
428  $flashMsgs = [];
429  # We do not need to handle unknown values since all are used.
430  foreach ( $flashDecode as $subTag => $subValue ) {
431  # We do not need any message for zeroed values.
432  if ( $subTag != 'fired' && $subValue == 0 ) {
433  continue;
434  }
435  $fullTag = $tag . '-' . $subTag;
436  $flashMsgs[] = $this->exifMsg( $fullTag, $subValue );
437  }
438  $val = $this->getLanguage()->commaList( $flashMsgs );
439  break;
440 
441  case 'FocalPlaneResolutionUnit':
442  switch ( $val ) {
443  case 2:
444  $val = $this->exifMsg( $tag, $val );
445  break;
446  default:
447  /* If not recognized, display as is. */
448  break;
449  }
450  break;
451 
452  case 'SensingMethod':
453  switch ( $val ) {
454  case 1:
455  case 2:
456  case 3:
457  case 4:
458  case 5:
459  case 7:
460  case 8:
461  $val = $this->exifMsg( $tag, $val );
462  break;
463  default:
464  /* If not recognized, display as is. */
465  break;
466  }
467  break;
468 
469  case 'FileSource':
470  switch ( $val ) {
471  case 3:
472  $val = $this->exifMsg( $tag, $val );
473  break;
474  default:
475  /* If not recognized, display as is. */
476  break;
477  }
478  break;
479 
480  case 'SceneType':
481  switch ( $val ) {
482  case 1:
483  $val = $this->exifMsg( $tag, $val );
484  break;
485  default:
486  /* If not recognized, display as is. */
487  break;
488  }
489  break;
490 
491  case 'CustomRendered':
492  switch ( $val ) {
493  case 0:
494  case 1:
495  $val = $this->exifMsg( $tag, $val );
496  break;
497  default:
498  /* If not recognized, display as is. */
499  break;
500  }
501  break;
502 
503  case 'ExposureMode':
504  switch ( $val ) {
505  case 0:
506  case 1:
507  case 2:
508  $val = $this->exifMsg( $tag, $val );
509  break;
510  default:
511  /* If not recognized, display as is. */
512  break;
513  }
514  break;
515 
516  case 'WhiteBalance':
517  switch ( $val ) {
518  case 0:
519  case 1:
520  $val = $this->exifMsg( $tag, $val );
521  break;
522  default:
523  /* If not recognized, display as is. */
524  break;
525  }
526  break;
527 
528  case 'SceneCaptureType':
529  switch ( $val ) {
530  case 0:
531  case 1:
532  case 2:
533  case 3:
534  $val = $this->exifMsg( $tag, $val );
535  break;
536  default:
537  /* If not recognized, display as is. */
538  break;
539  }
540  break;
541 
542  case 'GainControl':
543  switch ( $val ) {
544  case 0:
545  case 1:
546  case 2:
547  case 3:
548  case 4:
549  $val = $this->exifMsg( $tag, $val );
550  break;
551  default:
552  /* If not recognized, display as is. */
553  break;
554  }
555  break;
556 
557  case 'Contrast':
558  switch ( $val ) {
559  case 0:
560  case 1:
561  case 2:
562  $val = $this->exifMsg( $tag, $val );
563  break;
564  default:
565  /* If not recognized, display as is. */
566  break;
567  }
568  break;
569 
570  case 'Saturation':
571  switch ( $val ) {
572  case 0:
573  case 1:
574  case 2:
575  $val = $this->exifMsg( $tag, $val );
576  break;
577  default:
578  /* If not recognized, display as is. */
579  break;
580  }
581  break;
582 
583  case 'Sharpness':
584  switch ( $val ) {
585  case 0:
586  case 1:
587  case 2:
588  $val = $this->exifMsg( $tag, $val );
589  break;
590  default:
591  /* If not recognized, display as is. */
592  break;
593  }
594  break;
595 
596  case 'SubjectDistanceRange':
597  switch ( $val ) {
598  case 0:
599  case 1:
600  case 2:
601  case 3:
602  $val = $this->exifMsg( $tag, $val );
603  break;
604  default:
605  /* If not recognized, display as is. */
606  break;
607  }
608  break;
609 
610  // The GPS...Ref values are kept for compatibility, probably won't be reached.
611  case 'GPSLatitudeRef':
612  case 'GPSDestLatitudeRef':
613  switch ( $val ) {
614  case 'N':
615  case 'S':
616  $val = $this->exifMsg( 'GPSLatitude', $val );
617  break;
618  default:
619  /* If not recognized, display as is. */
620  break;
621  }
622  break;
623 
624  case 'GPSLongitudeRef':
625  case 'GPSDestLongitudeRef':
626  switch ( $val ) {
627  case 'E':
628  case 'W':
629  $val = $this->exifMsg( 'GPSLongitude', $val );
630  break;
631  default:
632  /* If not recognized, display as is. */
633  break;
634  }
635  break;
636 
637  case 'GPSAltitude':
638  if ( $val < 0 ) {
639  $val = $this->exifMsg( 'GPSAltitude', 'below-sealevel', $this->formatNum( -$val, 3 ) );
640  } else {
641  $val = $this->exifMsg( 'GPSAltitude', 'above-sealevel', $this->formatNum( $val, 3 ) );
642  }
643  break;
644 
645  case 'GPSStatus':
646  switch ( $val ) {
647  case 'A':
648  case 'V':
649  $val = $this->exifMsg( $tag, $val );
650  break;
651  default:
652  /* If not recognized, display as is. */
653  break;
654  }
655  break;
656 
657  case 'GPSMeasureMode':
658  switch ( $val ) {
659  case 2:
660  case 3:
661  $val = $this->exifMsg( $tag, $val );
662  break;
663  default:
664  /* If not recognized, display as is. */
665  break;
666  }
667  break;
668 
669  case 'GPSTrackRef':
670  case 'GPSImgDirectionRef':
671  case 'GPSDestBearingRef':
672  switch ( $val ) {
673  case 'T':
674  case 'M':
675  $val = $this->exifMsg( 'GPSDirection', $val );
676  break;
677  default:
678  /* If not recognized, display as is. */
679  break;
680  }
681  break;
682 
683  case 'GPSLatitude':
684  case 'GPSDestLatitude':
685  $val = $this->formatCoords( $val, 'latitude' );
686  break;
687  case 'GPSLongitude':
688  case 'GPSDestLongitude':
689  $val = $this->formatCoords( $val, 'longitude' );
690  break;
691 
692  case 'GPSSpeedRef':
693  switch ( $val ) {
694  case 'K':
695  case 'M':
696  case 'N':
697  $val = $this->exifMsg( 'GPSSpeed', $val );
698  break;
699  default:
700  /* If not recognized, display as is. */
701  break;
702  }
703  break;
704 
705  case 'GPSDestDistanceRef':
706  switch ( $val ) {
707  case 'K':
708  case 'M':
709  case 'N':
710  $val = $this->exifMsg( 'GPSDestDistance', $val );
711  break;
712  default:
713  /* If not recognized, display as is. */
714  break;
715  }
716  break;
717 
718  case 'GPSDOP':
719  // See https://en.wikipedia.org/wiki/Dilution_of_precision_(GPS)
720  if ( $val <= 2 ) {
721  $val = $this->exifMsg( $tag, 'excellent', $this->formatNum( $val ) );
722  } elseif ( $val <= 5 ) {
723  $val = $this->exifMsg( $tag, 'good', $this->formatNum( $val ) );
724  } elseif ( $val <= 10 ) {
725  $val = $this->exifMsg( $tag, 'moderate', $this->formatNum( $val ) );
726  } elseif ( $val <= 20 ) {
727  $val = $this->exifMsg( $tag, 'fair', $this->formatNum( $val ) );
728  } else {
729  $val = $this->exifMsg( $tag, 'poor', $this->formatNum( $val ) );
730  }
731  break;
732 
733  // This is not in the Exif standard, just a special
734  // case for our purposes which enables wikis to wikify
735  // the make, model and software name to link to their articles.
736  case 'Make':
737  case 'Model':
738  $val = $this->exifMsg( $tag, '', $val );
739  break;
740 
741  case 'Software':
742  if ( is_array( $val ) ) {
743  if ( count( $val ) > 1 ) {
744  // if its a software, version array.
745  $val = $this->msg( 'exif-software-version-value', $val[0], $val[1] )->text();
746  } else {
747  // https://phabricator.wikimedia.org/T178130
748  $val = $this->exifMsg( $tag, '', $val[0] );
749  }
750  } else {
751  $val = $this->exifMsg( $tag, '', $val );
752  }
753  break;
754 
755  case 'ExposureTime':
756  // Show the pretty fraction as well as decimal version
757  $val = $this->msg( 'exif-exposuretime-format',
758  $this->formatFraction( $val ), $this->formatNum( $val ) )->text();
759  break;
760  case 'ISOSpeedRatings':
761  // If its = 65535 that means its at the
762  // limit of the size of Exif::short and
763  // is really higher.
764  if ( $val == '65535' ) {
765  $val = $this->exifMsg( $tag, 'overflow' );
766  } else {
767  $val = $this->formatNum( $val );
768  }
769  break;
770  case 'FNumber':
771  $val = $this->msg( 'exif-fnumber-format',
772  $this->formatNum( $val ) )->text();
773  break;
774 
775  case 'FocalLength':
776  case 'FocalLengthIn35mmFilm':
777  $val = $this->msg( 'exif-focallength-format',
778  $this->formatNum( $val ) )->text();
779  break;
780 
781  case 'MaxApertureValue':
782  if ( strpos( $val, '/' ) !== false ) {
783  // need to expand this earlier to calculate fNumber
784  list( $n, $d ) = explode( '/', $val );
785  if ( is_numeric( $n ) && is_numeric( $d ) ) {
786  $val = $n / $d;
787  }
788  }
789  if ( is_numeric( $val ) ) {
790  $fNumber = pow( 2, $val / 2 );
791  if ( $fNumber !== false ) {
792  $val = $this->msg( 'exif-maxaperturevalue-value',
793  $this->formatNum( $val ),
794  $this->formatNum( $fNumber, 2 )
795  )->text();
796  }
797  }
798  break;
799 
800  case 'iimCategory':
801  switch ( strtolower( $val ) ) {
802  // See pg 29 of IPTC photo
803  // metadata standard.
804  case 'ace':
805  case 'clj':
806  case 'dis':
807  case 'fin':
808  case 'edu':
809  case 'evn':
810  case 'hth':
811  case 'hum':
812  case 'lab':
813  case 'lif':
814  case 'pol':
815  case 'rel':
816  case 'sci':
817  case 'soi':
818  case 'spo':
819  case 'war':
820  case 'wea':
821  $val = $this->exifMsg(
822  'iimcategory',
823  $val
824  );
825  }
826  break;
827  case 'SubjectNewsCode':
828  // Essentially like iimCategory.
829  // 8 (numeric) digit hierarchical
830  // classification. We decode the
831  // first 2 digits, which provide
832  // a broad category.
833  $val = $this->convertNewsCode( $val );
834  break;
835  case 'Urgency':
836  // 1-8 with 1 being highest, 5 normal
837  // 0 is reserved, and 9 is 'user-defined'.
838  $urgency = '';
839  if ( $val == 0 || $val == 9 ) {
840  $urgency = 'other';
841  } elseif ( $val < 5 && $val > 1 ) {
842  $urgency = 'high';
843  } elseif ( $val == 5 ) {
844  $urgency = 'normal';
845  } elseif ( $val <= 8 && $val > 5 ) {
846  $urgency = 'low';
847  }
848 
849  if ( $urgency !== '' ) {
850  $val = $this->exifMsg( 'urgency',
851  $urgency, $val
852  );
853  }
854  break;
855 
856  // Things that have a unit of pixels.
857  case 'OriginalImageHeight':
858  case 'OriginalImageWidth':
859  case 'PixelXDimension':
860  case 'PixelYDimension':
861  case 'ImageWidth':
862  case 'ImageLength':
863  $val = $this->formatNum( $val ) . ' ' . $this->msg( 'unit-pixel' )->text();
864  break;
865 
866  // Do not transform fields with pure text.
867  // For some languages the formatNum()
868  // conversion results to wrong output like
869  // foo,bar@example,com or fooÙ«bar@exampleÙ«com.
870  // Also some 'numeric' things like Scene codes
871  // are included here as we really don't want
872  // commas inserted.
873  case 'ImageDescription':
874  case 'UserComment':
875  case 'Artist':
876  case 'Copyright':
877  case 'RelatedSoundFile':
878  case 'ImageUniqueID':
879  case 'SpectralSensitivity':
880  case 'GPSSatellites':
881  case 'GPSVersionID':
882  case 'GPSMapDatum':
883  case 'Keywords':
884  case 'WorldRegionDest':
885  case 'CountryDest':
886  case 'CountryCodeDest':
887  case 'ProvinceOrStateDest':
888  case 'CityDest':
889  case 'SublocationDest':
890  case 'WorldRegionCreated':
891  case 'CountryCreated':
892  case 'CountryCodeCreated':
893  case 'ProvinceOrStateCreated':
894  case 'CityCreated':
895  case 'SublocationCreated':
896  case 'ObjectName':
897  case 'SpecialInstructions':
898  case 'Headline':
899  case 'Credit':
900  case 'Source':
901  case 'EditStatus':
902  case 'FixtureIdentifier':
903  case 'LocationDest':
904  case 'LocationDestCode':
905  case 'Writer':
906  case 'JPEGFileComment':
907  case 'iimSupplementalCategory':
908  case 'OriginalTransmissionRef':
909  case 'Identifier':
910  case 'dc-contributor':
911  case 'dc-coverage':
912  case 'dc-publisher':
913  case 'dc-relation':
914  case 'dc-rights':
915  case 'dc-source':
916  case 'dc-type':
917  case 'Lens':
918  case 'SerialNumber':
919  case 'CameraOwnerName':
920  case 'Label':
921  case 'Nickname':
922  case 'RightsCertificate':
923  case 'CopyrightOwner':
924  case 'UsageTerms':
925  case 'WebStatement':
926  case 'OriginalDocumentID':
927  case 'LicenseUrl':
928  case 'MorePermissionsUrl':
929  case 'AttributionUrl':
930  case 'PreferredAttributionName':
931  case 'PNGFileComment':
932  case 'Disclaimer':
933  case 'ContentWarning':
934  case 'GIFFileComment':
935  case 'SceneCode':
936  case 'IntellectualGenre':
937  case 'Event':
938  case 'OrginisationInImage':
939  case 'PersonInImage':
940 
941  $val = htmlspecialchars( $val );
942  break;
943 
944  case 'ObjectCycle':
945  switch ( $val ) {
946  case 'a':
947  case 'p':
948  case 'b':
949  $val = $this->exifMsg( $tag, $val );
950  break;
951  default:
952  $val = htmlspecialchars( $val );
953  break;
954  }
955  break;
956  case 'Copyrighted':
957  switch ( $val ) {
958  case 'True':
959  case 'False':
960  $val = $this->exifMsg( $tag, $val );
961  break;
962  }
963  break;
964  case 'Rating':
965  if ( $val == '-1' ) {
966  $val = $this->exifMsg( $tag, 'rejected' );
967  } else {
968  $val = $this->formatNum( $val );
969  }
970  break;
971 
972  case 'LanguageCode':
973  $lang = Language::fetchLanguageName( strtolower( $val ), $this->getLanguage()->getCode() );
974  if ( $lang ) {
975  $val = htmlspecialchars( $lang );
976  } else {
977  $val = htmlspecialchars( $val );
978  }
979  break;
980 
981  default:
982  $val = $this->formatNum( $val );
983  break;
984  }
985  }
986  // End formatting values, start flattening arrays.
987  $vals = $this->flattenArrayReal( $vals, $type );
988  }
989 
990  return $tags;
991  }
992 
1007  public static function flattenArrayContentLang( $vals, $type = 'ul',
1008  $noHtml = false, $context = false
1009  ) {
1011  $obj = new FormatMetadata;
1012  if ( $context ) {
1013  $obj->setContext( $context );
1014  }
1015  $context = new DerivativeContext( $obj->getContext() );
1016  $context->setLanguage( $wgContLang );
1017  $obj->setContext( $context );
1018 
1019  return $obj->flattenArrayReal( $vals, $type, $noHtml );
1020  }
1021 
1038  public function flattenArrayReal( $vals, $type = 'ul', $noHtml = false ) {
1039  if ( !is_array( $vals ) ) {
1040  return $vals; // do nothing if not an array;
1041  }
1042 
1043  if ( isset( $vals['_type'] ) ) {
1044  $type = $vals['_type'];
1045  unset( $vals['_type'] );
1046  }
1047 
1048  if ( !is_array( $vals ) ) {
1049  return $vals; // do nothing if not an array;
1050  } elseif ( count( $vals ) === 1 && $type !== 'lang' && isset( $vals[0] ) ) {
1051  return $vals[0];
1052  } elseif ( count( $vals ) === 0 ) {
1053  wfDebug( __METHOD__ . " metadata array with 0 elements!\n" );
1054 
1055  return ""; // paranoia. This should never happen
1056  } else {
1057  /* @todo FIXME: This should hide some of the list entries if there are
1058  * say more than four. Especially if a field is translated into 20
1059  * languages, we don't want to show them all by default
1060  */
1061  switch ( $type ) {
1062  case 'lang':
1063  // Display default, followed by ContLang,
1064  // followed by the rest in no particular
1065  // order.
1066 
1067  // Todo: hide some items if really long list.
1068 
1069  $content = '';
1070 
1071  $priorityLanguages = $this->getPriorityLanguages();
1072  $defaultItem = false;
1073  $defaultLang = false;
1074 
1075  // If default is set, save it for later,
1076  // as we don't know if it's equal to
1077  // one of the lang codes. (In xmp
1078  // you specify the language for a
1079  // default property by having both
1080  // a default prop, and one in the language
1081  // that are identical)
1082  if ( isset( $vals['x-default'] ) ) {
1083  $defaultItem = $vals['x-default'];
1084  unset( $vals['x-default'] );
1085  }
1086  foreach ( $priorityLanguages as $pLang ) {
1087  if ( isset( $vals[$pLang] ) ) {
1088  $isDefault = false;
1089  if ( $vals[$pLang] === $defaultItem ) {
1090  $defaultItem = false;
1091  $isDefault = true;
1092  }
1093  $content .= $this->langItem(
1094  $vals[$pLang], $pLang,
1095  $isDefault, $noHtml );
1096 
1097  unset( $vals[$pLang] );
1098 
1099  if ( $this->singleLang ) {
1100  return Html::rawElement( 'span',
1101  [ 'lang' => $pLang ], $vals[$pLang] );
1102  }
1103  }
1104  }
1105 
1106  // Now do the rest.
1107  foreach ( $vals as $lang => $item ) {
1108  if ( $item === $defaultItem ) {
1109  $defaultLang = $lang;
1110  continue;
1111  }
1112  $content .= $this->langItem( $item,
1113  $lang, false, $noHtml );
1114  if ( $this->singleLang ) {
1115  return Html::rawElement( 'span',
1116  [ 'lang' => $lang ], $item );
1117  }
1118  }
1119  if ( $defaultItem !== false ) {
1120  $content = $this->langItem( $defaultItem,
1121  $defaultLang, true, $noHtml ) .
1122  $content;
1123  if ( $this->singleLang ) {
1124  return $defaultItem;
1125  }
1126  }
1127  if ( $noHtml ) {
1128  return $content;
1129  }
1130 
1131  return '<ul class="metadata-langlist">' .
1132  $content .
1133  '</ul>';
1134  case 'ol':
1135  if ( $noHtml ) {
1136  return "\n#" . implode( "\n#", $vals );
1137  }
1138 
1139  return "<ol><li>" . implode( "</li>\n<li>", $vals ) . '</li></ol>';
1140  case 'ul':
1141  default:
1142  if ( $noHtml ) {
1143  return "\n*" . implode( "\n*", $vals );
1144  }
1145 
1146  return "<ul><li>" . implode( "</li>\n<li>", $vals ) . '</li></ul>';
1147  }
1148  }
1149  }
1150 
1161  private function langItem( $value, $lang, $default = false, $noHtml = false ) {
1162  if ( $lang === false && $default === false ) {
1163  throw new MWException( '$lang and $default cannot both '
1164  . 'be false.' );
1165  }
1166 
1167  if ( $noHtml ) {
1168  $wrappedValue = $value;
1169  } else {
1170  $wrappedValue = '<span class="mw-metadata-lang-value">'
1171  . $value . '</span>';
1172  }
1173 
1174  if ( $lang === false ) {
1175  $msg = $this->msg( 'metadata-langitem-default', $wrappedValue );
1176  if ( $noHtml ) {
1177  return $msg->text() . "\n\n";
1178  } /* else */
1179 
1180  return '<li class="mw-metadata-lang-default">'
1181  . $msg->text()
1182  . "</li>\n";
1183  }
1184 
1185  $lowLang = strtolower( $lang );
1186  $langName = Language::fetchLanguageName( $lowLang );
1187  if ( $langName === '' ) {
1188  // try just the base language name. (aka en-US -> en ).
1189  list( $langPrefix ) = explode( '-', $lowLang, 2 );
1190  $langName = Language::fetchLanguageName( $langPrefix );
1191  if ( $langName === '' ) {
1192  // give up.
1193  $langName = $lang;
1194  }
1195  }
1196  // else we have a language specified
1197 
1198  $msg = $this->msg( 'metadata-langitem', $wrappedValue, $langName, $lang );
1199  if ( $noHtml ) {
1200  return '*' . $msg->text();
1201  } /* else: */
1202 
1203  $item = '<li class="mw-metadata-lang-code-'
1204  . $lang;
1205  if ( $default ) {
1206  $item .= ' mw-metadata-lang-default';
1207  }
1208  $item .= '" lang="' . $lang . '">';
1209  $item .= $msg->text();
1210  $item .= "</li>\n";
1211 
1212  return $item;
1213  }
1214 
1224  private function exifMsg( $tag, $val, $arg = null, $arg2 = null ) {
1226 
1227  if ( $val === '' ) {
1228  $val = 'value';
1229  }
1230 
1231  return $this->msg( $wgContLang->lc( "exif-$tag-$val" ), $arg, $arg2 )->text();
1232  }
1233 
1242  private function formatNum( $num, $round = false ) {
1243  $m = [];
1244  if ( is_array( $num ) ) {
1245  $out = [];
1246  foreach ( $num as $number ) {
1247  $out[] = $this->formatNum( $number );
1248  }
1249 
1250  return $this->getLanguage()->commaList( $out );
1251  }
1252  if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
1253  if ( $m[2] != 0 ) {
1254  $newNum = $m[1] / $m[2];
1255  if ( $round !== false ) {
1256  $newNum = round( $newNum, $round );
1257  }
1258  } else {
1259  $newNum = $num;
1260  }
1261 
1262  return $this->getLanguage()->formatNum( $newNum );
1263  } else {
1264  if ( is_numeric( $num ) && $round !== false ) {
1265  $num = round( $num, $round );
1266  }
1267 
1268  return $this->getLanguage()->formatNum( $num );
1269  }
1270  }
1271 
1278  private function formatFraction( $num ) {
1279  $m = [];
1280  if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
1281  $numerator = intval( $m[1] );
1282  $denominator = intval( $m[2] );
1283  $gcd = $this->gcd( abs( $numerator ), $denominator );
1284  if ( $gcd != 0 ) {
1285  // 0 shouldn't happen! ;)
1286  return $this->formatNum( $numerator / $gcd ) . '/' . $this->formatNum( $denominator / $gcd );
1287  }
1288  }
1289 
1290  return $this->formatNum( $num );
1291  }
1292 
1300  private function gcd( $a, $b ) {
1301  /*
1302  // https://en.wikipedia.org/wiki/Euclidean_algorithm
1303  // Recursive form would be:
1304  if( $b == 0 )
1305  return $a;
1306  else
1307  return gcd( $b, $a % $b );
1308  */
1309  while ( $b != 0 ) {
1310  $remainder = $a % $b;
1311 
1312  // tail recursion...
1313  $a = $b;
1314  $b = $remainder;
1315  }
1316 
1317  return $a;
1318  }
1319 
1332  private function convertNewsCode( $val ) {
1333  if ( !preg_match( '/^\d{8}$/D', $val ) ) {
1334  // Not a valid news code.
1335  return $val;
1336  }
1337  $cat = '';
1338  switch ( substr( $val, 0, 2 ) ) {
1339  case '01':
1340  $cat = 'ace';
1341  break;
1342  case '02':
1343  $cat = 'clj';
1344  break;
1345  case '03':
1346  $cat = 'dis';
1347  break;
1348  case '04':
1349  $cat = 'fin';
1350  break;
1351  case '05':
1352  $cat = 'edu';
1353  break;
1354  case '06':
1355  $cat = 'evn';
1356  break;
1357  case '07':
1358  $cat = 'hth';
1359  break;
1360  case '08':
1361  $cat = 'hum';
1362  break;
1363  case '09':
1364  $cat = 'lab';
1365  break;
1366  case '10':
1367  $cat = 'lif';
1368  break;
1369  case '11':
1370  $cat = 'pol';
1371  break;
1372  case '12':
1373  $cat = 'rel';
1374  break;
1375  case '13':
1376  $cat = 'sci';
1377  break;
1378  case '14':
1379  $cat = 'soi';
1380  break;
1381  case '15':
1382  $cat = 'spo';
1383  break;
1384  case '16':
1385  $cat = 'war';
1386  break;
1387  case '17':
1388  $cat = 'wea';
1389  break;
1390  }
1391  if ( $cat !== '' ) {
1392  $catMsg = $this->exifMsg( 'iimcategory', $cat );
1393  $val = $this->exifMsg( 'subjectnewscode', '', $val, $catMsg );
1394  }
1395 
1396  return $val;
1397  }
1398 
1407  private function formatCoords( $coord, $type ) {
1408  $ref = '';
1409  if ( $coord < 0 ) {
1410  $nCoord = -$coord;
1411  if ( $type === 'latitude' ) {
1412  $ref = 'S';
1413  } elseif ( $type === 'longitude' ) {
1414  $ref = 'W';
1415  }
1416  } else {
1417  $nCoord = $coord;
1418  if ( $type === 'latitude' ) {
1419  $ref = 'N';
1420  } elseif ( $type === 'longitude' ) {
1421  $ref = 'E';
1422  }
1423  }
1424 
1425  $deg = floor( $nCoord );
1426  $min = floor( ( $nCoord - $deg ) * 60.0 );
1427  $sec = round( ( ( $nCoord - $deg ) - $min / 60 ) * 3600, 2 );
1428 
1429  $deg = $this->formatNum( $deg );
1430  $min = $this->formatNum( $min );
1431  $sec = $this->formatNum( $sec );
1432 
1433  return $this->msg( 'exif-coordinate-format', $deg, $min, $sec, $ref, $coord )->text();
1434  }
1435 
1450  public function collapseContactInfo( $vals ) {
1451  if ( !( isset( $vals['CiAdrExtadr'] )
1452  || isset( $vals['CiAdrCity'] )
1453  || isset( $vals['CiAdrCtry'] )
1454  || isset( $vals['CiEmailWork'] )
1455  || isset( $vals['CiTelWork'] )
1456  || isset( $vals['CiAdrPcode'] )
1457  || isset( $vals['CiAdrRegion'] )
1458  || isset( $vals['CiUrlWork'] )
1459  ) ) {
1460  // We don't have any sub-properties
1461  // This could happen if its using old
1462  // iptc that just had this as a free-form
1463  // text value.
1464  // Note: We run this through htmlspecialchars
1465  // partially to be consistent, and partially
1466  // because people often insert >, etc into
1467  // the metadata which should not be interpreted
1468  // but we still want to auto-link urls.
1469  foreach ( $vals as &$val ) {
1470  $val = htmlspecialchars( $val );
1471  }
1472 
1473  return $this->flattenArrayReal( $vals );
1474  } else {
1475  // We have a real ContactInfo field.
1476  // Its unclear if all these fields have to be
1477  // set, so assume they do not.
1478  $url = $tel = $street = $city = $country = '';
1479  $email = $postal = $region = '';
1480 
1481  // Also note, some of the class names this uses
1482  // are similar to those used by hCard. This is
1483  // mostly because they're sensible names. This
1484  // does not (and does not attempt to) output
1485  // stuff in the hCard microformat. However it
1486  // might output in the adr microformat.
1487 
1488  if ( isset( $vals['CiAdrExtadr'] ) ) {
1489  // Todo: This can potentially be multi-line.
1490  // Need to check how that works in XMP.
1491  $street = '<span class="extended-address">'
1492  . htmlspecialchars(
1493  $vals['CiAdrExtadr'] )
1494  . '</span>';
1495  }
1496  if ( isset( $vals['CiAdrCity'] ) ) {
1497  $city = '<span class="locality">'
1498  . htmlspecialchars( $vals['CiAdrCity'] )
1499  . '</span>';
1500  }
1501  if ( isset( $vals['CiAdrCtry'] ) ) {
1502  $country = '<span class="country-name">'
1503  . htmlspecialchars( $vals['CiAdrCtry'] )
1504  . '</span>';
1505  }
1506  if ( isset( $vals['CiEmailWork'] ) ) {
1507  $emails = [];
1508  // Have to split multiple emails at commas/new lines.
1509  $splitEmails = explode( "\n", $vals['CiEmailWork'] );
1510  foreach ( $splitEmails as $e1 ) {
1511  // Also split on comma
1512  foreach ( explode( ',', $e1 ) as $e2 ) {
1513  $finalEmail = trim( $e2 );
1514  if ( $finalEmail == ',' || $finalEmail == '' ) {
1515  continue;
1516  }
1517  if ( strpos( $finalEmail, '<' ) !== false ) {
1518  // Don't do fancy formatting to
1519  // "My name" <foo@bar.com> style stuff
1520  $emails[] = $finalEmail;
1521  } else {
1522  $emails[] = '[mailto:'
1523  . $finalEmail
1524  . ' <span class="email">'
1525  . $finalEmail
1526  . '</span>]';
1527  }
1528  }
1529  }
1530  $email = implode( ', ', $emails );
1531  }
1532  if ( isset( $vals['CiTelWork'] ) ) {
1533  $tel = '<span class="tel">'
1534  . htmlspecialchars( $vals['CiTelWork'] )
1535  . '</span>';
1536  }
1537  if ( isset( $vals['CiAdrPcode'] ) ) {
1538  $postal = '<span class="postal-code">'
1539  . htmlspecialchars(
1540  $vals['CiAdrPcode'] )
1541  . '</span>';
1542  }
1543  if ( isset( $vals['CiAdrRegion'] ) ) {
1544  // Note this is province/state.
1545  $region = '<span class="region">'
1546  . htmlspecialchars(
1547  $vals['CiAdrRegion'] )
1548  . '</span>';
1549  }
1550  if ( isset( $vals['CiUrlWork'] ) ) {
1551  $url = '<span class="url">'
1552  . htmlspecialchars( $vals['CiUrlWork'] )
1553  . '</span>';
1554  }
1555 
1556  return $this->msg( 'exif-contact-value', $email, $url,
1557  $street, $city, $region, $postal, $country,
1558  $tel )->text();
1559  }
1560  }
1561 
1568  public static function getVisibleFields() {
1569  $fields = [];
1570  $lines = explode( "\n", wfMessage( 'metadata-fields' )->inContentLanguage()->text() );
1571  foreach ( $lines as $line ) {
1572  $matches = [];
1573  if ( preg_match( '/^\\*\s*(.*?)\s*$/', $line, $matches ) ) {
1574  $fields[] = $matches[1];
1575  }
1576  }
1577  $fields = array_map( 'strtolower', $fields );
1578 
1579  return $fields;
1580  }
1581 
1589  public function fetchExtendedMetadata( File $file ) {
1590  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1591 
1592  // If revision deleted, exit immediately
1593  if ( $file->isDeleted( File::DELETED_FILE ) ) {
1594  return [];
1595  }
1596 
1597  $cacheKey = $cache->makeKey(
1598  'getExtendedMetadata',
1599  $this->getLanguage()->getCode(),
1600  (int)$this->singleLang,
1601  $file->getSha1()
1602  );
1603 
1604  $cachedValue = $cache->get( $cacheKey );
1605  if (
1606  $cachedValue
1607  && Hooks::run( 'ValidateExtendedMetadataCache', [ $cachedValue['timestamp'], $file ] )
1608  ) {
1609  $extendedMetadata = $cachedValue['data'];
1610  } else {
1611  $maxCacheTime = ( $file instanceof ForeignAPIFile ) ? 60 * 60 * 12 : 60 * 60 * 24 * 30;
1612  $fileMetadata = $this->getExtendedMetadataFromFile( $file );
1613  $extendedMetadata = $this->getExtendedMetadataFromHook( $file, $fileMetadata, $maxCacheTime );
1614  if ( $this->singleLang ) {
1615  $this->resolveMultilangMetadata( $extendedMetadata );
1616  }
1617  $this->discardMultipleValues( $extendedMetadata );
1618  // Make sure the metadata won't break the API when an XML format is used.
1619  // This is an API-specific function so it would be cleaner to call it from
1620  // outside fetchExtendedMetadata, but this way we don't need to redo the
1621  // computation on a cache hit.
1622  $this->sanitizeArrayForAPI( $extendedMetadata );
1623  $valueToCache = [ 'data' => $extendedMetadata, 'timestamp' => wfTimestampNow() ];
1624  $cache->set( $cacheKey, $valueToCache, $maxCacheTime );
1625  }
1626 
1627  return $extendedMetadata;
1628  }
1629 
1639  protected function getExtendedMetadataFromFile( File $file ) {
1640  // If this is a remote file accessed via an API request, we already
1641  // have remote metadata so we just ignore any local one
1642  if ( $file instanceof ForeignAPIFile ) {
1643  // In case of error we pretend no metadata - this will get cached.
1644  // Might or might not be a good idea.
1645  return $file->getExtendedMetadata() ?: [];
1646  }
1647 
1648  $uploadDate = wfTimestamp( TS_ISO_8601, $file->getTimestamp() );
1649 
1650  $fileMetadata = [
1651  // This is modification time, which is close to "upload" time.
1652  'DateTime' => [
1653  'value' => $uploadDate,
1654  'source' => 'mediawiki-metadata',
1655  ],
1656  ];
1657 
1658  $title = $file->getTitle();
1659  if ( $title ) {
1660  $text = $title->getText();
1661  $pos = strrpos( $text, '.' );
1662 
1663  if ( $pos ) {
1664  $name = substr( $text, 0, $pos );
1665  } else {
1666  $name = $text;
1667  }
1668 
1669  $fileMetadata['ObjectName'] = [
1670  'value' => $name,
1671  'source' => 'mediawiki-metadata',
1672  ];
1673  }
1674 
1675  return $fileMetadata;
1676  }
1677 
1688  protected function getExtendedMetadataFromHook( File $file, array $extendedMetadata,
1689  &$maxCacheTime
1690  ) {
1691  Hooks::run( 'GetExtendedMetadata', [
1692  &$extendedMetadata,
1693  $file,
1694  $this->getContext(),
1695  $this->singleLang,
1696  &$maxCacheTime
1697  ] );
1698 
1699  $visible = array_flip( self::getVisibleFields() );
1700  foreach ( $extendedMetadata as $key => $value ) {
1701  if ( !isset( $visible[strtolower( $key )] ) ) {
1702  $extendedMetadata[$key]['hidden'] = '';
1703  }
1704  }
1705 
1706  return $extendedMetadata;
1707  }
1708 
1717  protected function resolveMultilangValue( $value ) {
1718  if (
1719  !is_array( $value )
1720  || !isset( $value['_type'] )
1721  || $value['_type'] != 'lang'
1722  ) {
1723  return $value; // do nothing if not a multilang array
1724  }
1725 
1726  // choose the language best matching user or site settings
1727  $priorityLanguages = $this->getPriorityLanguages();
1728  foreach ( $priorityLanguages as $lang ) {
1729  if ( isset( $value[$lang] ) ) {
1730  return $value[$lang];
1731  }
1732  }
1733 
1734  // otherwise go with the default language, if set
1735  if ( isset( $value['x-default'] ) ) {
1736  return $value['x-default'];
1737  }
1738 
1739  // otherwise just return any one language
1740  unset( $value['_type'] );
1741  if ( !empty( $value ) ) {
1742  return reset( $value );
1743  }
1744 
1745  // this should not happen; signal error
1746  return null;
1747  }
1748 
1758  protected function resolveMultivalueValue( $value ) {
1759  if ( !is_array( $value ) ) {
1760  return $value;
1761  } elseif ( isset( $value['_type'] ) && $value['_type'] === 'lang' ) {
1762  // if this is a multilang array, process fields separately
1763  $newValue = [];
1764  foreach ( $value as $k => $v ) {
1765  $newValue[$k] = $this->resolveMultivalueValue( $v );
1766  }
1767  return $newValue;
1768  } else { // _type is 'ul' or 'ol' or missing in which case it defaults to 'ul'
1769  $v = reset( $value );
1770  if ( key( $value ) === '_type' ) {
1771  $v = next( $value );
1772  }
1773  return $v;
1774  }
1775  }
1776 
1783  protected function resolveMultilangMetadata( &$metadata ) {
1784  if ( !is_array( $metadata ) ) {
1785  return;
1786  }
1787  foreach ( $metadata as &$field ) {
1788  if ( isset( $field['value'] ) ) {
1789  $field['value'] = $this->resolveMultilangValue( $field['value'] );
1790  }
1791  }
1792  }
1793 
1800  protected function discardMultipleValues( &$metadata ) {
1801  if ( !is_array( $metadata ) ) {
1802  return;
1803  }
1804  foreach ( $metadata as $key => &$field ) {
1805  if ( $key === 'Software' || $key === 'Contact' ) {
1806  // we skip some fields which have composite values. They are not particularly interesting
1807  // and you can get them via the metadata / commonmetadata APIs anyway.
1808  continue;
1809  }
1810  if ( isset( $field['value'] ) ) {
1811  $field['value'] = $this->resolveMultivalueValue( $field['value'] );
1812  }
1813  }
1814  }
1815 
1820  protected function sanitizeArrayForAPI( &$arr ) {
1821  if ( !is_array( $arr ) ) {
1822  return;
1823  }
1824 
1825  $counter = 1;
1826  foreach ( $arr as $key => &$value ) {
1827  $sanitizedKey = $this->sanitizeKeyForAPI( $key );
1828  if ( $sanitizedKey !== $key ) {
1829  if ( isset( $arr[$sanitizedKey] ) ) {
1830  // Make the sanitized keys hopefully unique.
1831  // To make it definitely unique would be too much effort, given that
1832  // sanitizing is only needed for misformatted metadata anyway, but
1833  // this at least covers the case when $arr is numeric.
1834  $sanitizedKey .= $counter;
1835  ++$counter;
1836  }
1837  $arr[$sanitizedKey] = $arr[$key];
1838  unset( $arr[$key] );
1839  }
1840  if ( is_array( $value ) ) {
1841  $this->sanitizeArrayForAPI( $value );
1842  }
1843  }
1844 
1845  // Handle API metadata keys (particularly "_type")
1846  $keys = array_filter( array_keys( $arr ), 'ApiResult::isMetadataKey' );
1847  if ( $keys ) {
1849  }
1850  }
1851 
1858  protected function sanitizeKeyForAPI( $key ) {
1859  // drop all characters which are not valid in an XML tag name
1860  // a bunch of non-ASCII letters would be valid but probably won't
1861  // be used so we take the easy way
1862  $key = preg_replace( '/[^a-zA-z0-9_:.-]/', '', $key );
1863  // drop characters which are invalid at the first position
1864  $key = preg_replace( '/^[\d-.]+/', '', $key );
1865 
1866  if ( $key == '' ) {
1867  $key = '_';
1868  }
1869 
1870  // special case for an internal keyword
1871  if ( $key == '_element' ) {
1872  $key = 'element';
1873  }
1874 
1875  return $key;
1876  }
1877 
1884  protected function getPriorityLanguages() {
1885  $priorityLanguages =
1887  $priorityLanguages = array_merge(
1888  (array)$this->getLanguage()->getCode(),
1889  $priorityLanguages[0],
1890  $priorityLanguages[1]
1891  );
1892 
1893  return $priorityLanguages;
1894  }
1895 }
ContextSource\$context
IContextSource $context
Definition: ContextSource.php:33
ContextSource\getContext
getContext()
Get the base IContextSource object.
Definition: ContextSource.php:40
FormatMetadata\sanitizeKeyForAPI
sanitizeKeyForAPI( $key)
Turns a string into a valid API identifier.
Definition: FormatMetadata.php:1858
FormatMetadata\gcd
gcd( $a, $b)
Calculate the greatest common divisor of two integers.
Definition: FormatMetadata.php:1300
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
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:33
captcha-old.count
count
Definition: captcha-old.py:249
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:12
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:1968
FormatMetadata\formatNum
formatNum( $num, $round=false)
Format a number, convert numbers from fractions into floating point numbers, joins arrays of numbers ...
Definition: FormatMetadata.php:1242
FormatMetadata\fetchExtendedMetadata
fetchExtendedMetadata(File $file)
Get an array of extended metadata.
Definition: FormatMetadata.php:1589
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
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
$s
$s
Definition: mergeMessageFileList.php:187
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
FormatMetadata\getExtendedMetadataFromFile
getExtendedMetadataFromFile(File $file)
Get file-based metadata in standardized format.
Definition: FormatMetadata.php:1639
File\isDeleted
isDeleted( $field)
Is this file a "deleted" file in a private archive? STUB.
Definition: File.php:1895
FormatMetadata\formatFraction
formatFraction( $num)
Format a rational number, reducing fractions.
Definition: FormatMetadata.php:1278
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:35
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:25
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:1800
FormatMetadata\convertNewsCode
convertNewsCode( $val)
Fetch the human readable version of a news code.
Definition: FormatMetadata.php:1332
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
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:934
FormatMetadata\resolveMultivalueValue
resolveMultivalueValue( $value)
Turns an XMP-style multivalue array into a single value by dropping all but the first value.
Definition: FormatMetadata.php:1758
FormatMetadata\formatCoords
formatCoords( $coord, $type)
Format a coordinate value, convert numbers from floating point into degree minute second representati...
Definition: FormatMetadata.php:1407
FormatMetadata\getPriorityLanguages
getPriorityLanguages()
Returns a list of languages (first is best) to use when formatting multilang fields,...
Definition: FormatMetadata.php:1884
FormatMetadata\langItem
langItem( $value, $lang, $default=false, $noHtml=false)
Helper function for creating lists of translations.
Definition: FormatMetadata.php:1161
$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:1688
$lines
$lines
Definition: router.php:61
$time
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1795
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:1997
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:982
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:1450
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
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2163
$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:1007
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:1224
FormatMetadata\sanitizeArrayForAPI
sanitizeArrayForAPI(&$arr)
Makes sure the given array is a valid API response fragment.
Definition: FormatMetadata.php:1820
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
FormatMetadata\resolveMultilangMetadata
resolveMultilangMetadata(&$metadata)
Takes an array returned by the getExtendedMetadata* functions, and resolves multi-language values in ...
Definition: FormatMetadata.php:1783
$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:4552
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:9
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:1038
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
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:23
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:1568
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:1717
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
array
the array() calling protocol came about after MediaWiki 1.4rc1.
$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:56
$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:783
$type
$type
Definition: testCompression.php:48