MediaWiki REL1_28
FormatMetadata.php
Go to the documentation of this file.
1<?php
55 protected $singleLang = false;
56
63 public function setSingleLanguage( $val ) {
64 $this->singleLang = $val;
65 }
66
80 public static function getFormattedData( $tags, $context = false ) {
81 $obj = new FormatMetadata;
82 if ( $context ) {
83 $obj->setContext( $context );
84 }
85
86 return $obj->makeFormattedData( $tags );
87 }
88
100 public function makeFormattedData( $tags ) {
101 $resolutionunit = !isset( $tags['ResolutionUnit'] ) || $tags['ResolutionUnit'] == 2 ? 2 : 3;
102 unset( $tags['ResolutionUnit'] );
103
104 foreach ( $tags as $tag => &$vals ) {
105
106 // This seems ugly to wrap non-array's in an array just to unwrap again,
107 // especially when most of the time it is not an array
108 if ( !is_array( $tags[$tag] ) ) {
109 $vals = [ $vals ];
110 }
111
112 // _type is a special value to say what array type
113 if ( isset( $tags[$tag]['_type'] ) ) {
114 $type = $tags[$tag]['_type'];
115 unset( $vals['_type'] );
116 } else {
117 $type = 'ul'; // default unordered list.
118 }
119
120 // This is done differently as the tag is an array.
121 if ( $tag == 'GPSTimeStamp' && count( $vals ) === 3 ) {
122 // hour min sec array
123
124 $h = explode( '/', $vals[0] );
125 $m = explode( '/', $vals[1] );
126 $s = explode( '/', $vals[2] );
127
128 // this should already be validated
129 // when loaded from file, but it could
130 // come from a foreign repo, so be
131 // paranoid.
132 if ( !isset( $h[1] )
133 || !isset( $m[1] )
134 || !isset( $s[1] )
135 || $h[1] == 0
136 || $m[1] == 0
137 || $s[1] == 0
138 ) {
139 continue;
140 }
141 $tags[$tag] = str_pad( intval( $h[0] / $h[1] ), 2, '0', STR_PAD_LEFT )
142 . ':' . str_pad( intval( $m[0] / $m[1] ), 2, '0', STR_PAD_LEFT )
143 . ':' . str_pad( intval( $s[0] / $s[1] ), 2, '0', STR_PAD_LEFT );
144
145 try {
146 $time = wfTimestamp( TS_MW, '1971:01:01 ' . $tags[$tag] );
147 // the 1971:01:01 is just a placeholder, and not shown to user.
148 if ( $time && intval( $time ) > 0 ) {
149 $tags[$tag] = $this->getLanguage()->time( $time );
150 }
151 } catch ( TimestampException $e ) {
152 // This shouldn't happen, but we've seen bad formats
153 // such as 4-digit seconds in the wild.
154 // leave $tags[$tag] as-is
155 }
156 continue;
157 }
158
159 // The contact info is a multi-valued field
160 // instead of the other props which are single
161 // valued (mostly) so handle as a special case.
162 if ( $tag === 'Contact' ) {
163 $vals = $this->collapseContactInfo( $vals );
164 continue;
165 }
166
167 foreach ( $vals as &$val ) {
168
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 'Artist':
870 case 'Copyright':
871 case 'RelatedSoundFile':
872 case 'ImageUniqueID':
873 case 'SpectralSensitivity':
874 case 'GPSSatellites':
875 case 'GPSVersionID':
876 case 'GPSMapDatum':
877 case 'Keywords':
878 case 'WorldRegionDest':
879 case 'CountryDest':
880 case 'CountryCodeDest':
881 case 'ProvinceOrStateDest':
882 case 'CityDest':
883 case 'SublocationDest':
884 case 'WorldRegionCreated':
885 case 'CountryCreated':
886 case 'CountryCodeCreated':
887 case 'ProvinceOrStateCreated':
888 case 'CityCreated':
889 case 'SublocationCreated':
890 case 'ObjectName':
891 case 'SpecialInstructions':
892 case 'Headline':
893 case 'Credit':
894 case 'Source':
895 case 'EditStatus':
896 case 'FixtureIdentifier':
897 case 'LocationDest':
898 case 'LocationDestCode':
899 case 'Writer':
900 case 'JPEGFileComment':
901 case 'iimSupplementalCategory':
902 case 'OriginalTransmissionRef':
903 case 'Identifier':
904 case 'dc-contributor':
905 case 'dc-coverage':
906 case 'dc-publisher':
907 case 'dc-relation':
908 case 'dc-rights':
909 case 'dc-source':
910 case 'dc-type':
911 case 'Lens':
912 case 'SerialNumber':
913 case 'CameraOwnerName':
914 case 'Label':
915 case 'Nickname':
916 case 'RightsCertificate':
917 case 'CopyrightOwner':
918 case 'UsageTerms':
919 case 'WebStatement':
920 case 'OriginalDocumentID':
921 case 'LicenseUrl':
922 case 'MorePermissionsUrl':
923 case 'AttributionUrl':
924 case 'PreferredAttributionName':
925 case 'PNGFileComment':
926 case 'Disclaimer':
927 case 'ContentWarning':
928 case 'GIFFileComment':
929 case 'SceneCode':
930 case 'IntellectualGenre':
931 case 'Event':
932 case 'OrginisationInImage':
933 case 'PersonInImage':
934
935 $val = htmlspecialchars( $val );
936 break;
937
938 case 'ObjectCycle':
939 switch ( $val ) {
940 case 'a':
941 case 'p':
942 case 'b':
943 $val = $this->exifMsg( $tag, $val );
944 break;
945 default:
946 $val = htmlspecialchars( $val );
947 break;
948 }
949 break;
950 case 'Copyrighted':
951 switch ( $val ) {
952 case 'True':
953 case 'False':
954 $val = $this->exifMsg( $tag, $val );
955 break;
956 }
957 break;
958 case 'Rating':
959 if ( $val == '-1' ) {
960 $val = $this->exifMsg( $tag, 'rejected' );
961 } else {
962 $val = $this->formatNum( $val );
963 }
964 break;
965
966 case 'LanguageCode':
967 $lang = Language::fetchLanguageName( strtolower( $val ), $this->getLanguage()->getCode() );
968 if ( $lang ) {
969 $val = htmlspecialchars( $lang );
970 } else {
971 $val = htmlspecialchars( $val );
972 }
973 break;
974
975 default:
976 $val = $this->formatNum( $val );
977 break;
978 }
979 }
980 // End formatting values, start flattening arrays.
981 $vals = $this->flattenArrayReal( $vals, $type );
982 }
983
984 return $tags;
985 }
986
1001 public static function flattenArrayContentLang( $vals, $type = 'ul',
1002 $noHtml = false, $context = false
1003 ) {
1005 $obj = new FormatMetadata;
1006 if ( $context ) {
1007 $obj->setContext( $context );
1008 }
1009 $context = new DerivativeContext( $obj->getContext() );
1010 $context->setLanguage( $wgContLang );
1011 $obj->setContext( $context );
1012
1013 return $obj->flattenArrayReal( $vals, $type, $noHtml );
1014 }
1015
1032 public function flattenArrayReal( $vals, $type = 'ul', $noHtml = false ) {
1033 if ( !is_array( $vals ) ) {
1034 return $vals; // do nothing if not an array;
1035 }
1036
1037 if ( isset( $vals['_type'] ) ) {
1038 $type = $vals['_type'];
1039 unset( $vals['_type'] );
1040 }
1041
1042 if ( !is_array( $vals ) ) {
1043 return $vals; // do nothing if not an array;
1044 } elseif ( count( $vals ) === 1 && $type !== 'lang' ) {
1045 return $vals[0];
1046 } elseif ( count( $vals ) === 0 ) {
1047 wfDebug( __METHOD__ . " metadata array with 0 elements!\n" );
1048
1049 return ""; // paranoia. This should never happen
1050 } else {
1051 /* @todo FIXME: This should hide some of the list entries if there are
1052 * say more than four. Especially if a field is translated into 20
1053 * languages, we don't want to show them all by default
1054 */
1055 switch ( $type ) {
1056 case 'lang':
1057 // Display default, followed by ContLang,
1058 // followed by the rest in no particular
1059 // order.
1060
1061 // Todo: hide some items if really long list.
1062
1063 $content = '';
1064
1065 $priorityLanguages = $this->getPriorityLanguages();
1066 $defaultItem = false;
1067 $defaultLang = false;
1068
1069 // If default is set, save it for later,
1070 // as we don't know if it's equal to
1071 // one of the lang codes. (In xmp
1072 // you specify the language for a
1073 // default property by having both
1074 // a default prop, and one in the language
1075 // that are identical)
1076 if ( isset( $vals['x-default'] ) ) {
1077 $defaultItem = $vals['x-default'];
1078 unset( $vals['x-default'] );
1079 }
1080 foreach ( $priorityLanguages as $pLang ) {
1081 if ( isset( $vals[$pLang] ) ) {
1082 $isDefault = false;
1083 if ( $vals[$pLang] === $defaultItem ) {
1084 $defaultItem = false;
1085 $isDefault = true;
1086 }
1087 $content .= $this->langItem(
1088 $vals[$pLang], $pLang,
1089 $isDefault, $noHtml );
1090
1091 unset( $vals[$pLang] );
1092
1093 if ( $this->singleLang ) {
1094 return Html::rawElement( 'span',
1095 [ 'lang' => $pLang ], $vals[$pLang] );
1096 }
1097 }
1098 }
1099
1100 // Now do the rest.
1101 foreach ( $vals as $lang => $item ) {
1102 if ( $item === $defaultItem ) {
1103 $defaultLang = $lang;
1104 continue;
1105 }
1106 $content .= $this->langItem( $item,
1107 $lang, false, $noHtml );
1108 if ( $this->singleLang ) {
1109 return Html::rawElement( 'span',
1110 [ 'lang' => $lang ], $item );
1111 }
1112 }
1113 if ( $defaultItem !== false ) {
1114 $content = $this->langItem( $defaultItem,
1115 $defaultLang, true, $noHtml ) .
1116 $content;
1117 if ( $this->singleLang ) {
1118 return $defaultItem;
1119 }
1120 }
1121 if ( $noHtml ) {
1122 return $content;
1123 }
1124
1125 return '<ul class="metadata-langlist">' .
1126 $content .
1127 '</ul>';
1128 case 'ol':
1129 if ( $noHtml ) {
1130 return "\n#" . implode( "\n#", $vals );
1131 }
1132
1133 return "<ol><li>" . implode( "</li>\n<li>", $vals ) . '</li></ol>';
1134 case 'ul':
1135 default:
1136 if ( $noHtml ) {
1137 return "\n*" . implode( "\n*", $vals );
1138 }
1139
1140 return "<ul><li>" . implode( "</li>\n<li>", $vals ) . '</li></ul>';
1141 }
1142 }
1143 }
1144
1155 private function langItem( $value, $lang, $default = false, $noHtml = false ) {
1156 if ( $lang === false && $default === false ) {
1157 throw new MWException( '$lang and $default cannot both '
1158 . 'be false.' );
1159 }
1160
1161 if ( $noHtml ) {
1162 $wrappedValue = $value;
1163 } else {
1164 $wrappedValue = '<span class="mw-metadata-lang-value">'
1165 . $value . '</span>';
1166 }
1167
1168 if ( $lang === false ) {
1169 $msg = $this->msg( 'metadata-langitem-default', $wrappedValue );
1170 if ( $noHtml ) {
1171 return $msg->text() . "\n\n";
1172 } /* else */
1173
1174 return '<li class="mw-metadata-lang-default">'
1175 . $msg->text()
1176 . "</li>\n";
1177 }
1178
1179 $lowLang = strtolower( $lang );
1180 $langName = Language::fetchLanguageName( $lowLang );
1181 if ( $langName === '' ) {
1182 // try just the base language name. (aka en-US -> en ).
1183 list( $langPrefix ) = explode( '-', $lowLang, 2 );
1184 $langName = Language::fetchLanguageName( $langPrefix );
1185 if ( $langName === '' ) {
1186 // give up.
1187 $langName = $lang;
1188 }
1189 }
1190 // else we have a language specified
1191
1192 $msg = $this->msg( 'metadata-langitem', $wrappedValue, $langName, $lang );
1193 if ( $noHtml ) {
1194 return '*' . $msg->text();
1195 } /* else: */
1196
1197 $item = '<li class="mw-metadata-lang-code-'
1198 . $lang;
1199 if ( $default ) {
1200 $item .= ' mw-metadata-lang-default';
1201 }
1202 $item .= '" lang="' . $lang . '">';
1203 $item .= $msg->text();
1204 $item .= "</li>\n";
1205
1206 return $item;
1207 }
1208
1218 private function exifMsg( $tag, $val, $arg = null, $arg2 = null ) {
1220
1221 if ( $val === '' ) {
1222 $val = 'value';
1223 }
1224
1225 return $this->msg( $wgContLang->lc( "exif-$tag-$val" ), $arg, $arg2 )->text();
1226 }
1227
1236 private function formatNum( $num, $round = false ) {
1237 $m = [];
1238 if ( is_array( $num ) ) {
1239 $out = [];
1240 foreach ( $num as $number ) {
1241 $out[] = $this->formatNum( $number );
1242 }
1243
1244 return $this->getLanguage()->commaList( $out );
1245 }
1246 if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
1247 if ( $m[2] != 0 ) {
1248 $newNum = $m[1] / $m[2];
1249 if ( $round !== false ) {
1250 $newNum = round( $newNum, $round );
1251 }
1252 } else {
1253 $newNum = $num;
1254 }
1255
1256 return $this->getLanguage()->formatNum( $newNum );
1257 } else {
1258 if ( is_numeric( $num ) && $round !== false ) {
1259 $num = round( $num, $round );
1260 }
1261
1262 return $this->getLanguage()->formatNum( $num );
1263 }
1264 }
1265
1272 private function formatFraction( $num ) {
1273 $m = [];
1274 if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
1275 $numerator = intval( $m[1] );
1276 $denominator = intval( $m[2] );
1277 $gcd = $this->gcd( abs( $numerator ), $denominator );
1278 if ( $gcd != 0 ) {
1279 // 0 shouldn't happen! ;)
1280 return $this->formatNum( $numerator / $gcd ) . '/' . $this->formatNum( $denominator / $gcd );
1281 }
1282 }
1283
1284 return $this->formatNum( $num );
1285 }
1286
1294 private function gcd( $a, $b ) {
1295 /*
1296 // https://en.wikipedia.org/wiki/Euclidean_algorithm
1297 // Recursive form would be:
1298 if( $b == 0 )
1299 return $a;
1300 else
1301 return gcd( $b, $a % $b );
1302 */
1303 while ( $b != 0 ) {
1304 $remainder = $a % $b;
1305
1306 // tail recursion...
1307 $a = $b;
1308 $b = $remainder;
1309 }
1310
1311 return $a;
1312 }
1313
1326 private function convertNewsCode( $val ) {
1327 if ( !preg_match( '/^\d{8}$/D', $val ) ) {
1328 // Not a valid news code.
1329 return $val;
1330 }
1331 $cat = '';
1332 switch ( substr( $val, 0, 2 ) ) {
1333 case '01':
1334 $cat = 'ace';
1335 break;
1336 case '02':
1337 $cat = 'clj';
1338 break;
1339 case '03':
1340 $cat = 'dis';
1341 break;
1342 case '04':
1343 $cat = 'fin';
1344 break;
1345 case '05':
1346 $cat = 'edu';
1347 break;
1348 case '06':
1349 $cat = 'evn';
1350 break;
1351 case '07':
1352 $cat = 'hth';
1353 break;
1354 case '08':
1355 $cat = 'hum';
1356 break;
1357 case '09':
1358 $cat = 'lab';
1359 break;
1360 case '10':
1361 $cat = 'lif';
1362 break;
1363 case '11':
1364 $cat = 'pol';
1365 break;
1366 case '12':
1367 $cat = 'rel';
1368 break;
1369 case '13':
1370 $cat = 'sci';
1371 break;
1372 case '14':
1373 $cat = 'soi';
1374 break;
1375 case '15':
1376 $cat = 'spo';
1377 break;
1378 case '16':
1379 $cat = 'war';
1380 break;
1381 case '17':
1382 $cat = 'wea';
1383 break;
1384 }
1385 if ( $cat !== '' ) {
1386 $catMsg = $this->exifMsg( 'iimcategory', $cat );
1387 $val = $this->exifMsg( 'subjectnewscode', '', $val, $catMsg );
1388 }
1389
1390 return $val;
1391 }
1392
1401 private function formatCoords( $coord, $type ) {
1402 $ref = '';
1403 if ( $coord < 0 ) {
1404 $nCoord = -$coord;
1405 if ( $type === 'latitude' ) {
1406 $ref = 'S';
1407 } elseif ( $type === 'longitude' ) {
1408 $ref = 'W';
1409 }
1410 } else {
1411 $nCoord = $coord;
1412 if ( $type === 'latitude' ) {
1413 $ref = 'N';
1414 } elseif ( $type === 'longitude' ) {
1415 $ref = 'E';
1416 }
1417 }
1418
1419 $deg = floor( $nCoord );
1420 $min = floor( ( $nCoord - $deg ) * 60.0 );
1421 $sec = round( ( ( $nCoord - $deg ) - $min / 60 ) * 3600, 2 );
1422
1423 $deg = $this->formatNum( $deg );
1424 $min = $this->formatNum( $min );
1425 $sec = $this->formatNum( $sec );
1426
1427 return $this->msg( 'exif-coordinate-format', $deg, $min, $sec, $ref, $coord )->text();
1428 }
1429
1444 public function collapseContactInfo( $vals ) {
1445 if ( !( isset( $vals['CiAdrExtadr'] )
1446 || isset( $vals['CiAdrCity'] )
1447 || isset( $vals['CiAdrCtry'] )
1448 || isset( $vals['CiEmailWork'] )
1449 || isset( $vals['CiTelWork'] )
1450 || isset( $vals['CiAdrPcode'] )
1451 || isset( $vals['CiAdrRegion'] )
1452 || isset( $vals['CiUrlWork'] )
1453 ) ) {
1454 // We don't have any sub-properties
1455 // This could happen if its using old
1456 // iptc that just had this as a free-form
1457 // text value.
1458 // Note: We run this through htmlspecialchars
1459 // partially to be consistent, and partially
1460 // because people often insert >, etc into
1461 // the metadata which should not be interpreted
1462 // but we still want to auto-link urls.
1463 foreach ( $vals as &$val ) {
1464 $val = htmlspecialchars( $val );
1465 }
1466
1467 return $this->flattenArrayReal( $vals );
1468 } else {
1469 // We have a real ContactInfo field.
1470 // Its unclear if all these fields have to be
1471 // set, so assume they do not.
1472 $url = $tel = $street = $city = $country = '';
1473 $email = $postal = $region = '';
1474
1475 // Also note, some of the class names this uses
1476 // are similar to those used by hCard. This is
1477 // mostly because they're sensible names. This
1478 // does not (and does not attempt to) output
1479 // stuff in the hCard microformat. However it
1480 // might output in the adr microformat.
1481
1482 if ( isset( $vals['CiAdrExtadr'] ) ) {
1483 // Todo: This can potentially be multi-line.
1484 // Need to check how that works in XMP.
1485 $street = '<span class="extended-address">'
1486 . htmlspecialchars(
1487 $vals['CiAdrExtadr'] )
1488 . '</span>';
1489 }
1490 if ( isset( $vals['CiAdrCity'] ) ) {
1491 $city = '<span class="locality">'
1492 . htmlspecialchars( $vals['CiAdrCity'] )
1493 . '</span>';
1494 }
1495 if ( isset( $vals['CiAdrCtry'] ) ) {
1496 $country = '<span class="country-name">'
1497 . htmlspecialchars( $vals['CiAdrCtry'] )
1498 . '</span>';
1499 }
1500 if ( isset( $vals['CiEmailWork'] ) ) {
1501 $emails = [];
1502 // Have to split multiple emails at commas/new lines.
1503 $splitEmails = explode( "\n", $vals['CiEmailWork'] );
1504 foreach ( $splitEmails as $e1 ) {
1505 // Also split on comma
1506 foreach ( explode( ',', $e1 ) as $e2 ) {
1507 $finalEmail = trim( $e2 );
1508 if ( $finalEmail == ',' || $finalEmail == '' ) {
1509 continue;
1510 }
1511 if ( strpos( $finalEmail, '<' ) !== false ) {
1512 // Don't do fancy formatting to
1513 // "My name" <foo@bar.com> style stuff
1514 $emails[] = $finalEmail;
1515 } else {
1516 $emails[] = '[mailto:'
1517 . $finalEmail
1518 . ' <span class="email">'
1519 . $finalEmail
1520 . '</span>]';
1521 }
1522 }
1523 }
1524 $email = implode( ', ', $emails );
1525 }
1526 if ( isset( $vals['CiTelWork'] ) ) {
1527 $tel = '<span class="tel">'
1528 . htmlspecialchars( $vals['CiTelWork'] )
1529 . '</span>';
1530 }
1531 if ( isset( $vals['CiAdrPcode'] ) ) {
1532 $postal = '<span class="postal-code">'
1533 . htmlspecialchars(
1534 $vals['CiAdrPcode'] )
1535 . '</span>';
1536 }
1537 if ( isset( $vals['CiAdrRegion'] ) ) {
1538 // Note this is province/state.
1539 $region = '<span class="region">'
1540 . htmlspecialchars(
1541 $vals['CiAdrRegion'] )
1542 . '</span>';
1543 }
1544 if ( isset( $vals['CiUrlWork'] ) ) {
1545 $url = '<span class="url">'
1546 . htmlspecialchars( $vals['CiUrlWork'] )
1547 . '</span>';
1548 }
1549
1550 return $this->msg( 'exif-contact-value', $email, $url,
1551 $street, $city, $region, $postal, $country,
1552 $tel )->text();
1553 }
1554 }
1555
1562 public static function getVisibleFields() {
1563 $fields = [];
1564 $lines = explode( "\n", wfMessage( 'metadata-fields' )->inContentLanguage()->text() );
1565 foreach ( $lines as $line ) {
1566 $matches = [];
1567 if ( preg_match( '/^\\*\s*(.*?)\s*$/', $line, $matches ) ) {
1568 $fields[] = $matches[1];
1569 }
1570 }
1571 $fields = array_map( 'strtolower', $fields );
1572
1573 return $fields;
1574 }
1575
1583 public function fetchExtendedMetadata( File $file ) {
1584 $cache = ObjectCache::getMainWANInstance();
1585
1586 // If revision deleted, exit immediately
1587 if ( $file->isDeleted( File::DELETED_FILE ) ) {
1588 return [];
1589 }
1590
1591 $cacheKey = wfMemcKey(
1592 'getExtendedMetadata',
1593 $this->getLanguage()->getCode(),
1594 (int)$this->singleLang,
1595 $file->getSha1()
1596 );
1597
1598 $cachedValue = $cache->get( $cacheKey );
1599 if (
1600 $cachedValue
1601 && Hooks::run( 'ValidateExtendedMetadataCache', [ $cachedValue['timestamp'], $file ] )
1602 ) {
1603 $extendedMetadata = $cachedValue['data'];
1604 } else {
1605 $maxCacheTime = ( $file instanceof ForeignAPIFile ) ? 60 * 60 * 12 : 60 * 60 * 24 * 30;
1606 $fileMetadata = $this->getExtendedMetadataFromFile( $file );
1607 $extendedMetadata = $this->getExtendedMetadataFromHook( $file, $fileMetadata, $maxCacheTime );
1608 if ( $this->singleLang ) {
1609 $this->resolveMultilangMetadata( $extendedMetadata );
1610 }
1611 $this->discardMultipleValues( $extendedMetadata );
1612 // Make sure the metadata won't break the API when an XML format is used.
1613 // This is an API-specific function so it would be cleaner to call it from
1614 // outside fetchExtendedMetadata, but this way we don't need to redo the
1615 // computation on a cache hit.
1616 $this->sanitizeArrayForAPI( $extendedMetadata );
1617 $valueToCache = [ 'data' => $extendedMetadata, 'timestamp' => wfTimestampNow() ];
1618 $cache->set( $cacheKey, $valueToCache, $maxCacheTime );
1619 }
1620
1621 return $extendedMetadata;
1622 }
1623
1633 protected function getExtendedMetadataFromFile( File $file ) {
1634 // If this is a remote file accessed via an API request, we already
1635 // have remote metadata so we just ignore any local one
1636 if ( $file instanceof ForeignAPIFile ) {
1637 // In case of error we pretend no metadata - this will get cached.
1638 // Might or might not be a good idea.
1639 return $file->getExtendedMetadata() ?: [];
1640 }
1641
1642 $uploadDate = wfTimestamp( TS_ISO_8601, $file->getTimestamp() );
1643
1644 $fileMetadata = [
1645 // This is modification time, which is close to "upload" time.
1646 'DateTime' => [
1647 'value' => $uploadDate,
1648 'source' => 'mediawiki-metadata',
1649 ],
1650 ];
1651
1652 $title = $file->getTitle();
1653 if ( $title ) {
1654 $text = $title->getText();
1655 $pos = strrpos( $text, '.' );
1656
1657 if ( $pos ) {
1658 $name = substr( $text, 0, $pos );
1659 } else {
1660 $name = $text;
1661 }
1662
1663 $fileMetadata['ObjectName'] = [
1664 'value' => $name,
1665 'source' => 'mediawiki-metadata',
1666 ];
1667 }
1668
1669 return $fileMetadata;
1670 }
1671
1682 protected function getExtendedMetadataFromHook( File $file, array $extendedMetadata,
1683 &$maxCacheTime
1684 ) {
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 }
1811
1816 protected function sanitizeArrayForAPI( &$arr ) {
1817 if ( !is_array( $arr ) ) {
1818 return;
1819 }
1820
1821 $counter = 1;
1822 foreach ( $arr as $key => &$value ) {
1823 $sanitizedKey = $this->sanitizeKeyForAPI( $key );
1824 if ( $sanitizedKey !== $key ) {
1825 if ( isset( $arr[$sanitizedKey] ) ) {
1826 // Make the sanitized keys hopefully unique.
1827 // To make it definitely unique would be too much effort, given that
1828 // sanitizing is only needed for misformatted metadata anyway, but
1829 // this at least covers the case when $arr is numeric.
1830 $sanitizedKey .= $counter;
1831 ++$counter;
1832 }
1833 $arr[$sanitizedKey] = $arr[$key];
1834 unset( $arr[$key] );
1835 }
1836 if ( is_array( $value ) ) {
1837 $this->sanitizeArrayForAPI( $value );
1838 }
1839 }
1840
1841 // Handle API metadata keys (particularly "_type")
1842 $keys = array_filter( array_keys( $arr ), 'ApiResult::isMetadataKey' );
1843 if ( $keys ) {
1845 }
1846 }
1847
1854 protected function sanitizeKeyForAPI( $key ) {
1855 // drop all characters which are not valid in an XML tag name
1856 // a bunch of non-ASCII letters would be valid but probably won't
1857 // be used so we take the easy way
1858 $key = preg_replace( '/[^a-zA-z0-9_:.-]/', '', $key );
1859 // drop characters which are invalid at the first position
1860 $key = preg_replace( '/^[\d-.]+/', '', $key );
1861
1862 if ( $key == '' ) {
1863 $key = '_';
1864 }
1865
1866 // special case for an internal keyword
1867 if ( $key == '_element' ) {
1868 $key = 'element';
1869 }
1870
1871 return $key;
1872 }
1873
1880 protected function getPriorityLanguages() {
1881 $priorityLanguages =
1882 Language::getFallbacksIncludingSiteLanguage( $this->getLanguage()->getCode() );
1883 $priorityLanguages = array_merge(
1884 (array)$this->getLanguage()->getCode(),
1885 $priorityLanguages[0],
1886 $priorityLanguages[1]
1887 );
1888
1889 return $priorityLanguages;
1890 }
1891}
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.
wfMemcKey()
Make a cache key for the local wiki.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
$line
Definition cdb.php:59
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()
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:50
getTimestamp()
Get the 14-character timestamp of the file upload.
Definition File.php:2094
getTitle()
Return the associated title object.
Definition File.php:325
const DELETED_FILE
Definition File.php:52
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.
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.
namespace are movable Hooks may change this value to override the return value of MWNamespace::isMovable(). 'NewDifferenceEngine' do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition hooks.txt:2568
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition hooks.txt:1752
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:986
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content $content
Definition hooks.txt:1094
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:886
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books $tag
Definition hooks.txt:1033
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:304
returning false will NOT prevent logging $e
Definition hooks.txt:2110
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:67
if(!isset( $args[0])) $lang
const TS_ISO_8601
ISO 8601 format with no timezone: 1986-02-09T20:00:00Z.
Definition defines.php:28
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
Definition defines.php:11