MediaWiki REL1_30
FormatMetadata.php
Go to the documentation of this file.
1<?php
28use 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 its a software, version array.
744 $val = $this->msg( 'exif-software-version-value', $val[0], $val[1] )->text();
745 } else {
746 $val = $this->exifMsg( $tag, '', $val );
747 }
748 break;
749
750 case 'ExposureTime':
751 // Show the pretty fraction as well as decimal version
752 $val = $this->msg( 'exif-exposuretime-format',
753 $this->formatFraction( $val ), $this->formatNum( $val ) )->text();
754 break;
755 case 'ISOSpeedRatings':
756 // If its = 65535 that means its at the
757 // limit of the size of Exif::short and
758 // is really higher.
759 if ( $val == '65535' ) {
760 $val = $this->exifMsg( $tag, 'overflow' );
761 } else {
762 $val = $this->formatNum( $val );
763 }
764 break;
765 case 'FNumber':
766 $val = $this->msg( 'exif-fnumber-format',
767 $this->formatNum( $val ) )->text();
768 break;
769
770 case 'FocalLength':
771 case 'FocalLengthIn35mmFilm':
772 $val = $this->msg( 'exif-focallength-format',
773 $this->formatNum( $val ) )->text();
774 break;
775
776 case 'MaxApertureValue':
777 if ( strpos( $val, '/' ) !== false ) {
778 // need to expand this earlier to calculate fNumber
779 list( $n, $d ) = explode( '/', $val );
780 if ( is_numeric( $n ) && is_numeric( $d ) ) {
781 $val = $n / $d;
782 }
783 }
784 if ( is_numeric( $val ) ) {
785 $fNumber = pow( 2, $val / 2 );
786 if ( $fNumber !== false ) {
787 $val = $this->msg( 'exif-maxaperturevalue-value',
788 $this->formatNum( $val ),
789 $this->formatNum( $fNumber, 2 )
790 )->text();
791 }
792 }
793 break;
794
795 case 'iimCategory':
796 switch ( strtolower( $val ) ) {
797 // See pg 29 of IPTC photo
798 // metadata standard.
799 case 'ace':
800 case 'clj':
801 case 'dis':
802 case 'fin':
803 case 'edu':
804 case 'evn':
805 case 'hth':
806 case 'hum':
807 case 'lab':
808 case 'lif':
809 case 'pol':
810 case 'rel':
811 case 'sci':
812 case 'soi':
813 case 'spo':
814 case 'war':
815 case 'wea':
816 $val = $this->exifMsg(
817 'iimcategory',
818 $val
819 );
820 }
821 break;
822 case 'SubjectNewsCode':
823 // Essentially like iimCategory.
824 // 8 (numeric) digit hierarchical
825 // classification. We decode the
826 // first 2 digits, which provide
827 // a broad category.
828 $val = $this->convertNewsCode( $val );
829 break;
830 case 'Urgency':
831 // 1-8 with 1 being highest, 5 normal
832 // 0 is reserved, and 9 is 'user-defined'.
833 $urgency = '';
834 if ( $val == 0 || $val == 9 ) {
835 $urgency = 'other';
836 } elseif ( $val < 5 && $val > 1 ) {
837 $urgency = 'high';
838 } elseif ( $val == 5 ) {
839 $urgency = 'normal';
840 } elseif ( $val <= 8 && $val > 5 ) {
841 $urgency = 'low';
842 }
843
844 if ( $urgency !== '' ) {
845 $val = $this->exifMsg( 'urgency',
846 $urgency, $val
847 );
848 }
849 break;
850
851 // Things that have a unit of pixels.
852 case 'OriginalImageHeight':
853 case 'OriginalImageWidth':
854 case 'PixelXDimension':
855 case 'PixelYDimension':
856 case 'ImageWidth':
857 case 'ImageLength':
858 $val = $this->formatNum( $val ) . ' ' . $this->msg( 'unit-pixel' )->text();
859 break;
860
861 // Do not transform fields with pure text.
862 // For some languages the formatNum()
863 // conversion results to wrong output like
864 // foo,bar@example,com or fooÙ«bar@exampleÙ«com.
865 // Also some 'numeric' things like Scene codes
866 // are included here as we really don't want
867 // commas inserted.
868 case 'ImageDescription':
869 case 'UserComment':
870 case 'Artist':
871 case 'Copyright':
872 case 'RelatedSoundFile':
873 case 'ImageUniqueID':
874 case 'SpectralSensitivity':
875 case 'GPSSatellites':
876 case 'GPSVersionID':
877 case 'GPSMapDatum':
878 case 'Keywords':
879 case 'WorldRegionDest':
880 case 'CountryDest':
881 case 'CountryCodeDest':
882 case 'ProvinceOrStateDest':
883 case 'CityDest':
884 case 'SublocationDest':
885 case 'WorldRegionCreated':
886 case 'CountryCreated':
887 case 'CountryCodeCreated':
888 case 'ProvinceOrStateCreated':
889 case 'CityCreated':
890 case 'SublocationCreated':
891 case 'ObjectName':
892 case 'SpecialInstructions':
893 case 'Headline':
894 case 'Credit':
895 case 'Source':
896 case 'EditStatus':
897 case 'FixtureIdentifier':
898 case 'LocationDest':
899 case 'LocationDestCode':
900 case 'Writer':
901 case 'JPEGFileComment':
902 case 'iimSupplementalCategory':
903 case 'OriginalTransmissionRef':
904 case 'Identifier':
905 case 'dc-contributor':
906 case 'dc-coverage':
907 case 'dc-publisher':
908 case 'dc-relation':
909 case 'dc-rights':
910 case 'dc-source':
911 case 'dc-type':
912 case 'Lens':
913 case 'SerialNumber':
914 case 'CameraOwnerName':
915 case 'Label':
916 case 'Nickname':
917 case 'RightsCertificate':
918 case 'CopyrightOwner':
919 case 'UsageTerms':
920 case 'WebStatement':
921 case 'OriginalDocumentID':
922 case 'LicenseUrl':
923 case 'MorePermissionsUrl':
924 case 'AttributionUrl':
925 case 'PreferredAttributionName':
926 case 'PNGFileComment':
927 case 'Disclaimer':
928 case 'ContentWarning':
929 case 'GIFFileComment':
930 case 'SceneCode':
931 case 'IntellectualGenre':
932 case 'Event':
933 case 'OrginisationInImage':
934 case 'PersonInImage':
935
936 $val = htmlspecialchars( $val );
937 break;
938
939 case 'ObjectCycle':
940 switch ( $val ) {
941 case 'a':
942 case 'p':
943 case 'b':
944 $val = $this->exifMsg( $tag, $val );
945 break;
946 default:
947 $val = htmlspecialchars( $val );
948 break;
949 }
950 break;
951 case 'Copyrighted':
952 switch ( $val ) {
953 case 'True':
954 case 'False':
955 $val = $this->exifMsg( $tag, $val );
956 break;
957 }
958 break;
959 case 'Rating':
960 if ( $val == '-1' ) {
961 $val = $this->exifMsg( $tag, 'rejected' );
962 } else {
963 $val = $this->formatNum( $val );
964 }
965 break;
966
967 case 'LanguageCode':
968 $lang = Language::fetchLanguageName( strtolower( $val ), $this->getLanguage()->getCode() );
969 if ( $lang ) {
970 $val = htmlspecialchars( $lang );
971 } else {
972 $val = htmlspecialchars( $val );
973 }
974 break;
975
976 default:
977 $val = $this->formatNum( $val );
978 break;
979 }
980 }
981 // End formatting values, start flattening arrays.
982 $vals = $this->flattenArrayReal( $vals, $type );
983 }
984
985 return $tags;
986 }
987
1002 public static function flattenArrayContentLang( $vals, $type = 'ul',
1003 $noHtml = false, $context = false
1004 ) {
1006 $obj = new FormatMetadata;
1007 if ( $context ) {
1008 $obj->setContext( $context );
1009 }
1010 $context = new DerivativeContext( $obj->getContext() );
1011 $context->setLanguage( $wgContLang );
1012 $obj->setContext( $context );
1013
1014 return $obj->flattenArrayReal( $vals, $type, $noHtml );
1015 }
1016
1033 public function flattenArrayReal( $vals, $type = 'ul', $noHtml = false ) {
1034 if ( !is_array( $vals ) ) {
1035 return $vals; // do nothing if not an array;
1036 }
1037
1038 if ( isset( $vals['_type'] ) ) {
1039 $type = $vals['_type'];
1040 unset( $vals['_type'] );
1041 }
1042
1043 if ( !is_array( $vals ) ) {
1044 return $vals; // do nothing if not an array;
1045 } elseif ( count( $vals ) === 1 && $type !== 'lang' && isset( $vals[0] ) ) {
1046 return $vals[0];
1047 } elseif ( count( $vals ) === 0 ) {
1048 wfDebug( __METHOD__ . " metadata array with 0 elements!\n" );
1049
1050 return ""; // paranoia. This should never happen
1051 } else {
1052 /* @todo FIXME: This should hide some of the list entries if there are
1053 * say more than four. Especially if a field is translated into 20
1054 * languages, we don't want to show them all by default
1055 */
1056 switch ( $type ) {
1057 case 'lang':
1058 // Display default, followed by ContLang,
1059 // followed by the rest in no particular
1060 // order.
1061
1062 // Todo: hide some items if really long list.
1063
1064 $content = '';
1065
1066 $priorityLanguages = $this->getPriorityLanguages();
1067 $defaultItem = false;
1068 $defaultLang = false;
1069
1070 // If default is set, save it for later,
1071 // as we don't know if it's equal to
1072 // one of the lang codes. (In xmp
1073 // you specify the language for a
1074 // default property by having both
1075 // a default prop, and one in the language
1076 // that are identical)
1077 if ( isset( $vals['x-default'] ) ) {
1078 $defaultItem = $vals['x-default'];
1079 unset( $vals['x-default'] );
1080 }
1081 foreach ( $priorityLanguages as $pLang ) {
1082 if ( isset( $vals[$pLang] ) ) {
1083 $isDefault = false;
1084 if ( $vals[$pLang] === $defaultItem ) {
1085 $defaultItem = false;
1086 $isDefault = true;
1087 }
1088 $content .= $this->langItem(
1089 $vals[$pLang], $pLang,
1090 $isDefault, $noHtml );
1091
1092 unset( $vals[$pLang] );
1093
1094 if ( $this->singleLang ) {
1095 return Html::rawElement( 'span',
1096 [ 'lang' => $pLang ], $vals[$pLang] );
1097 }
1098 }
1099 }
1100
1101 // Now do the rest.
1102 foreach ( $vals as $lang => $item ) {
1103 if ( $item === $defaultItem ) {
1104 $defaultLang = $lang;
1105 continue;
1106 }
1107 $content .= $this->langItem( $item,
1108 $lang, false, $noHtml );
1109 if ( $this->singleLang ) {
1110 return Html::rawElement( 'span',
1111 [ 'lang' => $lang ], $item );
1112 }
1113 }
1114 if ( $defaultItem !== false ) {
1115 $content = $this->langItem( $defaultItem,
1116 $defaultLang, true, $noHtml ) .
1117 $content;
1118 if ( $this->singleLang ) {
1119 return $defaultItem;
1120 }
1121 }
1122 if ( $noHtml ) {
1123 return $content;
1124 }
1125
1126 return '<ul class="metadata-langlist">' .
1127 $content .
1128 '</ul>';
1129 case 'ol':
1130 if ( $noHtml ) {
1131 return "\n#" . implode( "\n#", $vals );
1132 }
1133
1134 return "<ol><li>" . implode( "</li>\n<li>", $vals ) . '</li></ol>';
1135 case 'ul':
1136 default:
1137 if ( $noHtml ) {
1138 return "\n*" . implode( "\n*", $vals );
1139 }
1140
1141 return "<ul><li>" . implode( "</li>\n<li>", $vals ) . '</li></ul>';
1142 }
1143 }
1144 }
1145
1156 private function langItem( $value, $lang, $default = false, $noHtml = false ) {
1157 if ( $lang === false && $default === false ) {
1158 throw new MWException( '$lang and $default cannot both '
1159 . 'be false.' );
1160 }
1161
1162 if ( $noHtml ) {
1163 $wrappedValue = $value;
1164 } else {
1165 $wrappedValue = '<span class="mw-metadata-lang-value">'
1166 . $value . '</span>';
1167 }
1168
1169 if ( $lang === false ) {
1170 $msg = $this->msg( 'metadata-langitem-default', $wrappedValue );
1171 if ( $noHtml ) {
1172 return $msg->text() . "\n\n";
1173 } /* else */
1174
1175 return '<li class="mw-metadata-lang-default">'
1176 . $msg->text()
1177 . "</li>\n";
1178 }
1179
1180 $lowLang = strtolower( $lang );
1181 $langName = Language::fetchLanguageName( $lowLang );
1182 if ( $langName === '' ) {
1183 // try just the base language name. (aka en-US -> en ).
1184 list( $langPrefix ) = explode( '-', $lowLang, 2 );
1185 $langName = Language::fetchLanguageName( $langPrefix );
1186 if ( $langName === '' ) {
1187 // give up.
1188 $langName = $lang;
1189 }
1190 }
1191 // else we have a language specified
1192
1193 $msg = $this->msg( 'metadata-langitem', $wrappedValue, $langName, $lang );
1194 if ( $noHtml ) {
1195 return '*' . $msg->text();
1196 } /* else: */
1197
1198 $item = '<li class="mw-metadata-lang-code-'
1199 . $lang;
1200 if ( $default ) {
1201 $item .= ' mw-metadata-lang-default';
1202 }
1203 $item .= '" lang="' . $lang . '">';
1204 $item .= $msg->text();
1205 $item .= "</li>\n";
1206
1207 return $item;
1208 }
1209
1219 private function exifMsg( $tag, $val, $arg = null, $arg2 = null ) {
1221
1222 if ( $val === '' ) {
1223 $val = 'value';
1224 }
1225
1226 return $this->msg( $wgContLang->lc( "exif-$tag-$val" ), $arg, $arg2 )->text();
1227 }
1228
1237 private function formatNum( $num, $round = false ) {
1238 $m = [];
1239 if ( is_array( $num ) ) {
1240 $out = [];
1241 foreach ( $num as $number ) {
1242 $out[] = $this->formatNum( $number );
1243 }
1244
1245 return $this->getLanguage()->commaList( $out );
1246 }
1247 if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
1248 if ( $m[2] != 0 ) {
1249 $newNum = $m[1] / $m[2];
1250 if ( $round !== false ) {
1251 $newNum = round( $newNum, $round );
1252 }
1253 } else {
1254 $newNum = $num;
1255 }
1256
1257 return $this->getLanguage()->formatNum( $newNum );
1258 } else {
1259 if ( is_numeric( $num ) && $round !== false ) {
1260 $num = round( $num, $round );
1261 }
1262
1263 return $this->getLanguage()->formatNum( $num );
1264 }
1265 }
1266
1273 private function formatFraction( $num ) {
1274 $m = [];
1275 if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
1276 $numerator = intval( $m[1] );
1277 $denominator = intval( $m[2] );
1278 $gcd = $this->gcd( abs( $numerator ), $denominator );
1279 if ( $gcd != 0 ) {
1280 // 0 shouldn't happen! ;)
1281 return $this->formatNum( $numerator / $gcd ) . '/' . $this->formatNum( $denominator / $gcd );
1282 }
1283 }
1284
1285 return $this->formatNum( $num );
1286 }
1287
1295 private function gcd( $a, $b ) {
1296 /*
1297 // https://en.wikipedia.org/wiki/Euclidean_algorithm
1298 // Recursive form would be:
1299 if( $b == 0 )
1300 return $a;
1301 else
1302 return gcd( $b, $a % $b );
1303 */
1304 while ( $b != 0 ) {
1305 $remainder = $a % $b;
1306
1307 // tail recursion...
1308 $a = $b;
1309 $b = $remainder;
1310 }
1311
1312 return $a;
1313 }
1314
1327 private function convertNewsCode( $val ) {
1328 if ( !preg_match( '/^\d{8}$/D', $val ) ) {
1329 // Not a valid news code.
1330 return $val;
1331 }
1332 $cat = '';
1333 switch ( substr( $val, 0, 2 ) ) {
1334 case '01':
1335 $cat = 'ace';
1336 break;
1337 case '02':
1338 $cat = 'clj';
1339 break;
1340 case '03':
1341 $cat = 'dis';
1342 break;
1343 case '04':
1344 $cat = 'fin';
1345 break;
1346 case '05':
1347 $cat = 'edu';
1348 break;
1349 case '06':
1350 $cat = 'evn';
1351 break;
1352 case '07':
1353 $cat = 'hth';
1354 break;
1355 case '08':
1356 $cat = 'hum';
1357 break;
1358 case '09':
1359 $cat = 'lab';
1360 break;
1361 case '10':
1362 $cat = 'lif';
1363 break;
1364 case '11':
1365 $cat = 'pol';
1366 break;
1367 case '12':
1368 $cat = 'rel';
1369 break;
1370 case '13':
1371 $cat = 'sci';
1372 break;
1373 case '14':
1374 $cat = 'soi';
1375 break;
1376 case '15':
1377 $cat = 'spo';
1378 break;
1379 case '16':
1380 $cat = 'war';
1381 break;
1382 case '17':
1383 $cat = 'wea';
1384 break;
1385 }
1386 if ( $cat !== '' ) {
1387 $catMsg = $this->exifMsg( 'iimcategory', $cat );
1388 $val = $this->exifMsg( 'subjectnewscode', '', $val, $catMsg );
1389 }
1390
1391 return $val;
1392 }
1393
1402 private function formatCoords( $coord, $type ) {
1403 $ref = '';
1404 if ( $coord < 0 ) {
1405 $nCoord = -$coord;
1406 if ( $type === 'latitude' ) {
1407 $ref = 'S';
1408 } elseif ( $type === 'longitude' ) {
1409 $ref = 'W';
1410 }
1411 } else {
1412 $nCoord = $coord;
1413 if ( $type === 'latitude' ) {
1414 $ref = 'N';
1415 } elseif ( $type === 'longitude' ) {
1416 $ref = 'E';
1417 }
1418 }
1419
1420 $deg = floor( $nCoord );
1421 $min = floor( ( $nCoord - $deg ) * 60.0 );
1422 $sec = round( ( ( $nCoord - $deg ) - $min / 60 ) * 3600, 2 );
1423
1424 $deg = $this->formatNum( $deg );
1425 $min = $this->formatNum( $min );
1426 $sec = $this->formatNum( $sec );
1427
1428 return $this->msg( 'exif-coordinate-format', $deg, $min, $sec, $ref, $coord )->text();
1429 }
1430
1445 public function collapseContactInfo( $vals ) {
1446 if ( !( isset( $vals['CiAdrExtadr'] )
1447 || isset( $vals['CiAdrCity'] )
1448 || isset( $vals['CiAdrCtry'] )
1449 || isset( $vals['CiEmailWork'] )
1450 || isset( $vals['CiTelWork'] )
1451 || isset( $vals['CiAdrPcode'] )
1452 || isset( $vals['CiAdrRegion'] )
1453 || isset( $vals['CiUrlWork'] )
1454 ) ) {
1455 // We don't have any sub-properties
1456 // This could happen if its using old
1457 // iptc that just had this as a free-form
1458 // text value.
1459 // Note: We run this through htmlspecialchars
1460 // partially to be consistent, and partially
1461 // because people often insert >, etc into
1462 // the metadata which should not be interpreted
1463 // but we still want to auto-link urls.
1464 foreach ( $vals as &$val ) {
1465 $val = htmlspecialchars( $val );
1466 }
1467
1468 return $this->flattenArrayReal( $vals );
1469 } else {
1470 // We have a real ContactInfo field.
1471 // Its unclear if all these fields have to be
1472 // set, so assume they do not.
1473 $url = $tel = $street = $city = $country = '';
1474 $email = $postal = $region = '';
1475
1476 // Also note, some of the class names this uses
1477 // are similar to those used by hCard. This is
1478 // mostly because they're sensible names. This
1479 // does not (and does not attempt to) output
1480 // stuff in the hCard microformat. However it
1481 // might output in the adr microformat.
1482
1483 if ( isset( $vals['CiAdrExtadr'] ) ) {
1484 // Todo: This can potentially be multi-line.
1485 // Need to check how that works in XMP.
1486 $street = '<span class="extended-address">'
1487 . htmlspecialchars(
1488 $vals['CiAdrExtadr'] )
1489 . '</span>';
1490 }
1491 if ( isset( $vals['CiAdrCity'] ) ) {
1492 $city = '<span class="locality">'
1493 . htmlspecialchars( $vals['CiAdrCity'] )
1494 . '</span>';
1495 }
1496 if ( isset( $vals['CiAdrCtry'] ) ) {
1497 $country = '<span class="country-name">'
1498 . htmlspecialchars( $vals['CiAdrCtry'] )
1499 . '</span>';
1500 }
1501 if ( isset( $vals['CiEmailWork'] ) ) {
1502 $emails = [];
1503 // Have to split multiple emails at commas/new lines.
1504 $splitEmails = explode( "\n", $vals['CiEmailWork'] );
1505 foreach ( $splitEmails as $e1 ) {
1506 // Also split on comma
1507 foreach ( explode( ',', $e1 ) as $e2 ) {
1508 $finalEmail = trim( $e2 );
1509 if ( $finalEmail == ',' || $finalEmail == '' ) {
1510 continue;
1511 }
1512 if ( strpos( $finalEmail, '<' ) !== false ) {
1513 // Don't do fancy formatting to
1514 // "My name" <foo@bar.com> style stuff
1515 $emails[] = $finalEmail;
1516 } else {
1517 $emails[] = '[mailto:'
1518 . $finalEmail
1519 . ' <span class="email">'
1520 . $finalEmail
1521 . '</span>]';
1522 }
1523 }
1524 }
1525 $email = implode( ', ', $emails );
1526 }
1527 if ( isset( $vals['CiTelWork'] ) ) {
1528 $tel = '<span class="tel">'
1529 . htmlspecialchars( $vals['CiTelWork'] )
1530 . '</span>';
1531 }
1532 if ( isset( $vals['CiAdrPcode'] ) ) {
1533 $postal = '<span class="postal-code">'
1534 . htmlspecialchars(
1535 $vals['CiAdrPcode'] )
1536 . '</span>';
1537 }
1538 if ( isset( $vals['CiAdrRegion'] ) ) {
1539 // Note this is province/state.
1540 $region = '<span class="region">'
1541 . htmlspecialchars(
1542 $vals['CiAdrRegion'] )
1543 . '</span>';
1544 }
1545 if ( isset( $vals['CiUrlWork'] ) ) {
1546 $url = '<span class="url">'
1547 . htmlspecialchars( $vals['CiUrlWork'] )
1548 . '</span>';
1549 }
1550
1551 return $this->msg( 'exif-contact-value', $email, $url,
1552 $street, $city, $region, $postal, $country,
1553 $tel )->text();
1554 }
1555 }
1556
1563 public static function getVisibleFields() {
1564 $fields = [];
1565 $lines = explode( "\n", wfMessage( 'metadata-fields' )->inContentLanguage()->text() );
1566 foreach ( $lines as $line ) {
1567 $matches = [];
1568 if ( preg_match( '/^\\*\s*(.*?)\s*$/', $line, $matches ) ) {
1569 $fields[] = $matches[1];
1570 }
1571 }
1572 $fields = array_map( 'strtolower', $fields );
1573
1574 return $fields;
1575 }
1576
1584 public function fetchExtendedMetadata( File $file ) {
1585 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1586
1587 // If revision deleted, exit immediately
1588 if ( $file->isDeleted( File::DELETED_FILE ) ) {
1589 return [];
1590 }
1591
1592 $cacheKey = $cache->makeKey(
1593 'getExtendedMetadata',
1594 $this->getLanguage()->getCode(),
1595 (int)$this->singleLang,
1596 $file->getSha1()
1597 );
1598
1599 $cachedValue = $cache->get( $cacheKey );
1600 if (
1601 $cachedValue
1602 && Hooks::run( 'ValidateExtendedMetadataCache', [ $cachedValue['timestamp'], $file ] )
1603 ) {
1604 $extendedMetadata = $cachedValue['data'];
1605 } else {
1606 $maxCacheTime = ( $file instanceof ForeignAPIFile ) ? 60 * 60 * 12 : 60 * 60 * 24 * 30;
1607 $fileMetadata = $this->getExtendedMetadataFromFile( $file );
1608 $extendedMetadata = $this->getExtendedMetadataFromHook( $file, $fileMetadata, $maxCacheTime );
1609 if ( $this->singleLang ) {
1610 $this->resolveMultilangMetadata( $extendedMetadata );
1611 }
1612 $this->discardMultipleValues( $extendedMetadata );
1613 // Make sure the metadata won't break the API when an XML format is used.
1614 // This is an API-specific function so it would be cleaner to call it from
1615 // outside fetchExtendedMetadata, but this way we don't need to redo the
1616 // computation on a cache hit.
1617 $this->sanitizeArrayForAPI( $extendedMetadata );
1618 $valueToCache = [ 'data' => $extendedMetadata, 'timestamp' => wfTimestampNow() ];
1619 $cache->set( $cacheKey, $valueToCache, $maxCacheTime );
1620 }
1621
1622 return $extendedMetadata;
1623 }
1624
1634 protected function getExtendedMetadataFromFile( File $file ) {
1635 // If this is a remote file accessed via an API request, we already
1636 // have remote metadata so we just ignore any local one
1637 if ( $file instanceof ForeignAPIFile ) {
1638 // In case of error we pretend no metadata - this will get cached.
1639 // Might or might not be a good idea.
1640 return $file->getExtendedMetadata() ?: [];
1641 }
1642
1643 $uploadDate = wfTimestamp( TS_ISO_8601, $file->getTimestamp() );
1644
1645 $fileMetadata = [
1646 // This is modification time, which is close to "upload" time.
1647 'DateTime' => [
1648 'value' => $uploadDate,
1649 'source' => 'mediawiki-metadata',
1650 ],
1651 ];
1652
1653 $title = $file->getTitle();
1654 if ( $title ) {
1655 $text = $title->getText();
1656 $pos = strrpos( $text, '.' );
1657
1658 if ( $pos ) {
1659 $name = substr( $text, 0, $pos );
1660 } else {
1661 $name = $text;
1662 }
1663
1664 $fileMetadata['ObjectName'] = [
1665 'value' => $name,
1666 'source' => 'mediawiki-metadata',
1667 ];
1668 }
1669
1670 return $fileMetadata;
1671 }
1672
1683 protected function getExtendedMetadataFromHook( File $file, array $extendedMetadata,
1684 &$maxCacheTime
1685 ) {
1686 Hooks::run( 'GetExtendedMetadata', [
1687 &$extendedMetadata,
1688 $file,
1689 $this->getContext(),
1690 $this->singleLang,
1691 &$maxCacheTime
1692 ] );
1693
1694 $visible = array_flip( self::getVisibleFields() );
1695 foreach ( $extendedMetadata as $key => $value ) {
1696 if ( !isset( $visible[strtolower( $key )] ) ) {
1697 $extendedMetadata[$key]['hidden'] = '';
1698 }
1699 }
1700
1701 return $extendedMetadata;
1702 }
1703
1712 protected function resolveMultilangValue( $value ) {
1713 if (
1714 !is_array( $value )
1715 || !isset( $value['_type'] )
1716 || $value['_type'] != 'lang'
1717 ) {
1718 return $value; // do nothing if not a multilang array
1719 }
1720
1721 // choose the language best matching user or site settings
1722 $priorityLanguages = $this->getPriorityLanguages();
1723 foreach ( $priorityLanguages as $lang ) {
1724 if ( isset( $value[$lang] ) ) {
1725 return $value[$lang];
1726 }
1727 }
1728
1729 // otherwise go with the default language, if set
1730 if ( isset( $value['x-default'] ) ) {
1731 return $value['x-default'];
1732 }
1733
1734 // otherwise just return any one language
1735 unset( $value['_type'] );
1736 if ( !empty( $value ) ) {
1737 return reset( $value );
1738 }
1739
1740 // this should not happen; signal error
1741 return null;
1742 }
1743
1753 protected function resolveMultivalueValue( $value ) {
1754 if ( !is_array( $value ) ) {
1755 return $value;
1756 } elseif ( isset( $value['_type'] ) && $value['_type'] === 'lang' ) {
1757 // if this is a multilang array, process fields separately
1758 $newValue = [];
1759 foreach ( $value as $k => $v ) {
1760 $newValue[$k] = $this->resolveMultivalueValue( $v );
1761 }
1762 return $newValue;
1763 } else { // _type is 'ul' or 'ol' or missing in which case it defaults to 'ul'
1764 list( $k, $v ) = each( $value );
1765 if ( $k === '_type' ) {
1766 $v = current( $value );
1767 }
1768 return $v;
1769 }
1770 }
1771
1778 protected function resolveMultilangMetadata( &$metadata ) {
1779 if ( !is_array( $metadata ) ) {
1780 return;
1781 }
1782 foreach ( $metadata as &$field ) {
1783 if ( isset( $field['value'] ) ) {
1784 $field['value'] = $this->resolveMultilangValue( $field['value'] );
1785 }
1786 }
1787 }
1788
1795 protected function discardMultipleValues( &$metadata ) {
1796 if ( !is_array( $metadata ) ) {
1797 return;
1798 }
1799 foreach ( $metadata as $key => &$field ) {
1800 if ( $key === 'Software' || $key === 'Contact' ) {
1801 // we skip some fields which have composite values. They are not particularly interesting
1802 // and you can get them via the metadata / commonmetadata APIs anyway.
1803 continue;
1804 }
1805 if ( isset( $field['value'] ) ) {
1806 $field['value'] = $this->resolveMultivalueValue( $field['value'] );
1807 }
1808 }
1809 }
1810
1815 protected function sanitizeArrayForAPI( &$arr ) {
1816 if ( !is_array( $arr ) ) {
1817 return;
1818 }
1819
1820 $counter = 1;
1821 foreach ( $arr as $key => &$value ) {
1822 $sanitizedKey = $this->sanitizeKeyForAPI( $key );
1823 if ( $sanitizedKey !== $key ) {
1824 if ( isset( $arr[$sanitizedKey] ) ) {
1825 // Make the sanitized keys hopefully unique.
1826 // To make it definitely unique would be too much effort, given that
1827 // sanitizing is only needed for misformatted metadata anyway, but
1828 // this at least covers the case when $arr is numeric.
1829 $sanitizedKey .= $counter;
1830 ++$counter;
1831 }
1832 $arr[$sanitizedKey] = $arr[$key];
1833 unset( $arr[$key] );
1834 }
1835 if ( is_array( $value ) ) {
1836 $this->sanitizeArrayForAPI( $value );
1837 }
1838 }
1839
1840 // Handle API metadata keys (particularly "_type")
1841 $keys = array_filter( array_keys( $arr ), 'ApiResult::isMetadataKey' );
1842 if ( $keys ) {
1844 }
1845 }
1846
1853 protected function sanitizeKeyForAPI( $key ) {
1854 // drop all characters which are not valid in an XML tag name
1855 // a bunch of non-ASCII letters would be valid but probably won't
1856 // be used so we take the easy way
1857 $key = preg_replace( '/[^a-zA-z0-9_:.-]/', '', $key );
1858 // drop characters which are invalid at the first position
1859 $key = preg_replace( '/^[\d-.]+/', '', $key );
1860
1861 if ( $key == '' ) {
1862 $key = '_';
1863 }
1864
1865 // special case for an internal keyword
1866 if ( $key == '_element' ) {
1867 $key = 'element';
1868 }
1869
1870 return $key;
1871 }
1872
1879 protected function getPriorityLanguages() {
1880 $priorityLanguages =
1881 Language::getFallbacksIncludingSiteLanguage( $this->getLanguage()->getCode() );
1882 $priorityLanguages = array_merge(
1883 (array)$this->getLanguage()->getCode(),
1884 $priorityLanguages[0],
1885 $priorityLanguages[1]
1886 );
1887
1888 return $priorityLanguages;
1889 }
1890}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
$line
Definition cdb.php:58
static setPreserveKeysList(array &$arr, $names)
Preserve specified keys.
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
msg( $key)
Get a Message object with context set Parameters are the same as wfMessage()
IContextSource $context
getLanguage()
Get the Language object.
getContext()
Get the base IContextSource object.
setContext(IContextSource $context)
Set the IContextSource object.
An IContextSource implementation which will inherit context from another source but allow individual ...
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition File.php:51
getTimestamp()
Get the 14-character timestamp of the file upload.
Definition File.php:2094
getTitle()
Return the associated title object.
Definition File.php:326
const DELETED_FILE
Definition File.php:53
getSha1()
Get the SHA-1 base 36 hash of the file.
Definition File.php:2116
isDeleted( $field)
Is this file a "deleted" file in a private archive? STUB.
Definition File.php:1874
Foreign file accessible through api.php requests.
Format Image metadata values into a human readable form.
resolveMultilangValue( $value)
Turns an XMP-style multilang array into a single value.
getPriorityLanguages()
Returns a list of languages (first is best) to use when formatting multilang fields,...
gcd( $a, $b)
Calculate the greatest common divisor of two integers.
formatCoords( $coord, $type)
Format a coordinate value, convert numbers from floating point into degree minute second representati...
formatFraction( $num)
Format a rational number, reducing fractions.
flattenArrayReal( $vals, $type='ul', $noHtml=false)
A function to collapse multivalued tags into a single value.
getExtendedMetadataFromFile(File $file)
Get file-based metadata in standardized format.
fetchExtendedMetadata(File $file)
Get an array of extended metadata.
convertNewsCode( $val)
Fetch the human readable version of a news code.
setSingleLanguage( $val)
Trigger only outputting single language for multilanguage fields.
sanitizeArrayForAPI(&$arr)
Makes sure the given array is a valid API response fragment.
langItem( $value, $lang, $default=false, $noHtml=false)
Helper function for creating lists of translations.
static flattenArrayContentLang( $vals, $type='ul', $noHtml=false, $context=false)
Flatten an array, using the content language for any messages.
discardMultipleValues(&$metadata)
Takes an array returned by the getExtendedMetadata* functions, and turns all fields into single-value...
makeFormattedData( $tags)
Numbers given by Exif user agents are often magical, that is they should be replaced by a detailed ex...
exifMsg( $tag, $val, $arg=null, $arg2=null)
Convenience function for getFormattedData()
resolveMultivalueValue( $value)
Turns an XMP-style multivalue array into a single value by dropping all but the first value.
formatNum( $num, $round=false)
Format a number, convert numbers from fractions into floating point numbers, joins arrays of numbers ...
collapseContactInfo( $vals)
Format the contact info field into a single value.
static getVisibleFields()
Get a list of fields that are visible by default.
getExtendedMetadataFromHook(File $file, array $extendedMetadata, &$maxCacheTime)
Get additional metadata from hooks in standardized format.
resolveMultilangMetadata(&$metadata)
Takes an array returned by the getExtendedMetadata* functions, and resolves multi-language values in ...
static getFormattedData( $tags, $context=false)
Numbers given by Exif user agents are often magical, that is they should be replaced by a detailed ex...
sanitizeKeyForAPI( $key)
Turns a string into a valid API identifier.
bool $singleLang
Only output a single language for multi-language fields.
MediaWiki exception.
MediaWikiServices is the service locator for the application scope of MediaWiki.
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
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 local content language as $wgContLang
Definition design.txt:57
when a variable name is used in a it is silently declared as a new local masking the global
Definition design.txt:95
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
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
the array() calling protocol came about after MediaWiki 1.4rc1.
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition hooks.txt:1778
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:962
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;div ...>$1&lt;/div>"). - flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException':Called before an exception(or PHP error) is logged. This is meant for integration with external error aggregation services
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:862
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:302
returning false will NOT prevent logging $e
Definition hooks.txt:2146
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
$cache
Definition mcc.php:33
$lines
Definition router.php:61
if(!isset( $args[0])) $lang