MediaWiki REL1_32
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 = (int)$val / 100;
275 break;
276
277 case 'ColorSpace':
278 switch ( $val ) {
279 case 1:
280 case 65535:
281 $val = $this->exifMsg( $tag, $val );
282 break;
283 default:
284 /* If not recognized, display as is. */
285 break;
286 }
287 break;
288
289 case 'ComponentsConfiguration':
290 switch ( $val ) {
291 case 0:
292 case 1:
293 case 2:
294 case 3:
295 case 4:
296 case 5:
297 case 6:
298 $val = $this->exifMsg( $tag, $val );
299 break;
300 default:
301 /* If not recognized, display as is. */
302 break;
303 }
304 break;
305
306 case 'DateTime':
307 case 'DateTimeOriginal':
308 case 'DateTimeDigitized':
309 case 'DateTimeReleased':
310 case 'DateTimeExpires':
311 case 'GPSDateStamp':
312 case 'dc-date':
313 case 'DateTimeMetadata':
314 if ( $val == '0000:00:00 00:00:00' || $val == ' : : : : ' ) {
315 $val = $this->msg( 'exif-unknowndate' )->text();
316 } elseif ( preg_match(
317 '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/D',
318 $val
319 ) ) {
320 // Full date.
321 $time = wfTimestamp( TS_MW, $val );
322 if ( $time && intval( $time ) > 0 ) {
323 $val = $this->getLanguage()->timeanddate( $time );
324 }
325 } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d)$/D', $val ) ) {
326 // No second field. Still format the same
327 // since timeanddate doesn't include seconds anyways,
328 // but second still available in api
329 $time = wfTimestamp( TS_MW, $val . ':00' );
330 if ( $time && intval( $time ) > 0 ) {
331 $val = $this->getLanguage()->timeanddate( $time );
332 }
333 } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d)$/D', $val ) ) {
334 // If only the date but not the time is filled in.
335 $time = wfTimestamp( TS_MW, substr( $val, 0, 4 )
336 . substr( $val, 5, 2 )
337 . substr( $val, 8, 2 )
338 . '000000' );
339 if ( $time && intval( $time ) > 0 ) {
340 $val = $this->getLanguage()->date( $time );
341 }
342 }
343 // else it will just output $val without formatting it.
344 break;
345
346 case 'ExposureProgram':
347 switch ( $val ) {
348 case 0:
349 case 1:
350 case 2:
351 case 3:
352 case 4:
353 case 5:
354 case 6:
355 case 7:
356 case 8:
357 $val = $this->exifMsg( $tag, $val );
358 break;
359 default:
360 /* If not recognized, display as is. */
361 break;
362 }
363 break;
364
365 case 'SubjectDistance':
366 $val = $this->exifMsg( $tag, '', $this->formatNum( $val ) );
367 break;
368
369 case 'MeteringMode':
370 switch ( $val ) {
371 case 0:
372 case 1:
373 case 2:
374 case 3:
375 case 4:
376 case 5:
377 case 6:
378 case 7:
379 case 255:
380 $val = $this->exifMsg( $tag, $val );
381 break;
382 default:
383 /* If not recognized, display as is. */
384 break;
385 }
386 break;
387
388 case 'LightSource':
389 switch ( $val ) {
390 case 0:
391 case 1:
392 case 2:
393 case 3:
394 case 4:
395 case 9:
396 case 10:
397 case 11:
398 case 12:
399 case 13:
400 case 14:
401 case 15:
402 case 17:
403 case 18:
404 case 19:
405 case 20:
406 case 21:
407 case 22:
408 case 23:
409 case 24:
410 case 255:
411 $val = $this->exifMsg( $tag, $val );
412 break;
413 default:
414 /* If not recognized, display as is. */
415 break;
416 }
417 break;
418
419 case 'Flash':
420 $flashDecode = [
421 'fired' => $val & 0b00000001,
422 'return' => ( $val & 0b00000110 ) >> 1,
423 'mode' => ( $val & 0b00011000 ) >> 3,
424 'function' => ( $val & 0b00100000 ) >> 5,
425 'redeye' => ( $val & 0b01000000 ) >> 6,
426 // 'reserved' => ( $val & 0b10000000 ) >> 7,
427 ];
428 $flashMsgs = [];
429 # We do not need to handle unknown values since all are used.
430 foreach ( $flashDecode as $subTag => $subValue ) {
431 # We do not need any message for zeroed values.
432 if ( $subTag != 'fired' && $subValue == 0 ) {
433 continue;
434 }
435 $fullTag = $tag . '-' . $subTag;
436 $flashMsgs[] = $this->exifMsg( $fullTag, $subValue );
437 }
438 $val = $this->getLanguage()->commaList( $flashMsgs );
439 break;
440
441 case 'FocalPlaneResolutionUnit':
442 switch ( $val ) {
443 case 2:
444 $val = $this->exifMsg( $tag, $val );
445 break;
446 default:
447 /* If not recognized, display as is. */
448 break;
449 }
450 break;
451
452 case 'SensingMethod':
453 switch ( $val ) {
454 case 1:
455 case 2:
456 case 3:
457 case 4:
458 case 5:
459 case 7:
460 case 8:
461 $val = $this->exifMsg( $tag, $val );
462 break;
463 default:
464 /* If not recognized, display as is. */
465 break;
466 }
467 break;
468
469 case 'FileSource':
470 switch ( $val ) {
471 case 3:
472 $val = $this->exifMsg( $tag, $val );
473 break;
474 default:
475 /* If not recognized, display as is. */
476 break;
477 }
478 break;
479
480 case 'SceneType':
481 switch ( $val ) {
482 case 1:
483 $val = $this->exifMsg( $tag, $val );
484 break;
485 default:
486 /* If not recognized, display as is. */
487 break;
488 }
489 break;
490
491 case 'CustomRendered':
492 switch ( $val ) {
493 case 0:
494 case 1:
495 $val = $this->exifMsg( $tag, $val );
496 break;
497 default:
498 /* If not recognized, display as is. */
499 break;
500 }
501 break;
502
503 case 'ExposureMode':
504 switch ( $val ) {
505 case 0:
506 case 1:
507 case 2:
508 $val = $this->exifMsg( $tag, $val );
509 break;
510 default:
511 /* If not recognized, display as is. */
512 break;
513 }
514 break;
515
516 case 'WhiteBalance':
517 switch ( $val ) {
518 case 0:
519 case 1:
520 $val = $this->exifMsg( $tag, $val );
521 break;
522 default:
523 /* If not recognized, display as is. */
524 break;
525 }
526 break;
527
528 case 'SceneCaptureType':
529 switch ( $val ) {
530 case 0:
531 case 1:
532 case 2:
533 case 3:
534 $val = $this->exifMsg( $tag, $val );
535 break;
536 default:
537 /* If not recognized, display as is. */
538 break;
539 }
540 break;
541
542 case 'GainControl':
543 switch ( $val ) {
544 case 0:
545 case 1:
546 case 2:
547 case 3:
548 case 4:
549 $val = $this->exifMsg( $tag, $val );
550 break;
551 default:
552 /* If not recognized, display as is. */
553 break;
554 }
555 break;
556
557 case 'Contrast':
558 switch ( $val ) {
559 case 0:
560 case 1:
561 case 2:
562 $val = $this->exifMsg( $tag, $val );
563 break;
564 default:
565 /* If not recognized, display as is. */
566 break;
567 }
568 break;
569
570 case 'Saturation':
571 switch ( $val ) {
572 case 0:
573 case 1:
574 case 2:
575 $val = $this->exifMsg( $tag, $val );
576 break;
577 default:
578 /* If not recognized, display as is. */
579 break;
580 }
581 break;
582
583 case 'Sharpness':
584 switch ( $val ) {
585 case 0:
586 case 1:
587 case 2:
588 $val = $this->exifMsg( $tag, $val );
589 break;
590 default:
591 /* If not recognized, display as is. */
592 break;
593 }
594 break;
595
596 case 'SubjectDistanceRange':
597 switch ( $val ) {
598 case 0:
599 case 1:
600 case 2:
601 case 3:
602 $val = $this->exifMsg( $tag, $val );
603 break;
604 default:
605 /* If not recognized, display as is. */
606 break;
607 }
608 break;
609
610 // The GPS...Ref values are kept for compatibility, probably won't be reached.
611 case 'GPSLatitudeRef':
612 case 'GPSDestLatitudeRef':
613 switch ( $val ) {
614 case 'N':
615 case 'S':
616 $val = $this->exifMsg( 'GPSLatitude', $val );
617 break;
618 default:
619 /* If not recognized, display as is. */
620 break;
621 }
622 break;
623
624 case 'GPSLongitudeRef':
625 case 'GPSDestLongitudeRef':
626 switch ( $val ) {
627 case 'E':
628 case 'W':
629 $val = $this->exifMsg( 'GPSLongitude', $val );
630 break;
631 default:
632 /* If not recognized, display as is. */
633 break;
634 }
635 break;
636
637 case 'GPSAltitude':
638 if ( $val < 0 ) {
639 $val = $this->exifMsg( 'GPSAltitude', 'below-sealevel', $this->formatNum( -$val, 3 ) );
640 } else {
641 $val = $this->exifMsg( 'GPSAltitude', 'above-sealevel', $this->formatNum( $val, 3 ) );
642 }
643 break;
644
645 case 'GPSStatus':
646 switch ( $val ) {
647 case 'A':
648 case 'V':
649 $val = $this->exifMsg( $tag, $val );
650 break;
651 default:
652 /* If not recognized, display as is. */
653 break;
654 }
655 break;
656
657 case 'GPSMeasureMode':
658 switch ( $val ) {
659 case 2:
660 case 3:
661 $val = $this->exifMsg( $tag, $val );
662 break;
663 default:
664 /* If not recognized, display as is. */
665 break;
666 }
667 break;
668
669 case 'GPSTrackRef':
670 case 'GPSImgDirectionRef':
671 case 'GPSDestBearingRef':
672 switch ( $val ) {
673 case 'T':
674 case 'M':
675 $val = $this->exifMsg( 'GPSDirection', $val );
676 break;
677 default:
678 /* If not recognized, display as is. */
679 break;
680 }
681 break;
682
683 case 'GPSLatitude':
684 case 'GPSDestLatitude':
685 $val = $this->formatCoords( $val, 'latitude' );
686 break;
687 case 'GPSLongitude':
688 case 'GPSDestLongitude':
689 $val = $this->formatCoords( $val, 'longitude' );
690 break;
691
692 case 'GPSSpeedRef':
693 switch ( $val ) {
694 case 'K':
695 case 'M':
696 case 'N':
697 $val = $this->exifMsg( 'GPSSpeed', $val );
698 break;
699 default:
700 /* If not recognized, display as is. */
701 break;
702 }
703 break;
704
705 case 'GPSDestDistanceRef':
706 switch ( $val ) {
707 case 'K':
708 case 'M':
709 case 'N':
710 $val = $this->exifMsg( 'GPSDestDistance', $val );
711 break;
712 default:
713 /* If not recognized, display as is. */
714 break;
715 }
716 break;
717
718 case 'GPSDOP':
719 // See https://en.wikipedia.org/wiki/Dilution_of_precision_(GPS)
720 if ( $val <= 2 ) {
721 $val = $this->exifMsg( $tag, 'excellent', $this->formatNum( $val ) );
722 } elseif ( $val <= 5 ) {
723 $val = $this->exifMsg( $tag, 'good', $this->formatNum( $val ) );
724 } elseif ( $val <= 10 ) {
725 $val = $this->exifMsg( $tag, 'moderate', $this->formatNum( $val ) );
726 } elseif ( $val <= 20 ) {
727 $val = $this->exifMsg( $tag, 'fair', $this->formatNum( $val ) );
728 } else {
729 $val = $this->exifMsg( $tag, 'poor', $this->formatNum( $val ) );
730 }
731 break;
732
733 // This is not in the Exif standard, just a special
734 // case for our purposes which enables wikis to wikify
735 // the make, model and software name to link to their articles.
736 case 'Make':
737 case 'Model':
738 $val = $this->exifMsg( $tag, '', $val );
739 break;
740
741 case 'Software':
742 if ( is_array( $val ) ) {
743 if ( count( $val ) > 1 ) {
744 // if its a software, version array.
745 $val = $this->msg( 'exif-software-version-value', $val[0], $val[1] )->text();
746 } else {
747 // https://phabricator.wikimedia.org/T178130
748 $val = $this->exifMsg( $tag, '', $val[0] );
749 }
750 } else {
751 $val = $this->exifMsg( $tag, '', $val );
752 }
753 break;
754
755 case 'ExposureTime':
756 // Show the pretty fraction as well as decimal version
757 $val = $this->msg( 'exif-exposuretime-format',
758 $this->formatFraction( $val ), $this->formatNum( $val ) )->text();
759 break;
760 case 'ISOSpeedRatings':
761 // If its = 65535 that means its at the
762 // limit of the size of Exif::short and
763 // is really higher.
764 if ( $val == '65535' ) {
765 $val = $this->exifMsg( $tag, 'overflow' );
766 } else {
767 $val = $this->formatNum( $val );
768 }
769 break;
770 case 'FNumber':
771 $val = $this->msg( 'exif-fnumber-format',
772 $this->formatNum( $val ) )->text();
773 break;
774
775 case 'FocalLength':
776 case 'FocalLengthIn35mmFilm':
777 $val = $this->msg( 'exif-focallength-format',
778 $this->formatNum( $val ) )->text();
779 break;
780
781 case 'MaxApertureValue':
782 if ( strpos( $val, '/' ) !== false ) {
783 // need to expand this earlier to calculate fNumber
784 list( $n, $d ) = explode( '/', $val );
785 if ( is_numeric( $n ) && is_numeric( $d ) ) {
786 $val = $n / $d;
787 }
788 }
789 if ( is_numeric( $val ) ) {
790 $fNumber = 2 ** ( $val / 2 );
791 if ( $fNumber !== false ) {
792 $val = $this->msg( 'exif-maxaperturevalue-value',
793 $this->formatNum( $val ),
794 $this->formatNum( $fNumber, 2 )
795 )->text();
796 }
797 }
798 break;
799
800 case 'iimCategory':
801 switch ( strtolower( $val ) ) {
802 // See pg 29 of IPTC photo
803 // metadata standard.
804 case 'ace':
805 case 'clj':
806 case 'dis':
807 case 'fin':
808 case 'edu':
809 case 'evn':
810 case 'hth':
811 case 'hum':
812 case 'lab':
813 case 'lif':
814 case 'pol':
815 case 'rel':
816 case 'sci':
817 case 'soi':
818 case 'spo':
819 case 'war':
820 case 'wea':
821 $val = $this->exifMsg(
822 'iimcategory',
823 $val
824 );
825 }
826 break;
827 case 'SubjectNewsCode':
828 // Essentially like iimCategory.
829 // 8 (numeric) digit hierarchical
830 // classification. We decode the
831 // first 2 digits, which provide
832 // a broad category.
833 $val = $this->convertNewsCode( $val );
834 break;
835 case 'Urgency':
836 // 1-8 with 1 being highest, 5 normal
837 // 0 is reserved, and 9 is 'user-defined'.
838 $urgency = '';
839 if ( $val == 0 || $val == 9 ) {
840 $urgency = 'other';
841 } elseif ( $val < 5 && $val > 1 ) {
842 $urgency = 'high';
843 } elseif ( $val == 5 ) {
844 $urgency = 'normal';
845 } elseif ( $val <= 8 && $val > 5 ) {
846 $urgency = 'low';
847 }
848
849 if ( $urgency !== '' ) {
850 $val = $this->exifMsg( 'urgency',
851 $urgency, $val
852 );
853 }
854 break;
855
856 // Things that have a unit of pixels.
857 case 'OriginalImageHeight':
858 case 'OriginalImageWidth':
859 case 'PixelXDimension':
860 case 'PixelYDimension':
861 case 'ImageWidth':
862 case 'ImageLength':
863 $val = $this->formatNum( $val ) . ' ' . $this->msg( 'unit-pixel' )->text();
864 break;
865
866 // Do not transform fields with pure text.
867 // For some languages the formatNum()
868 // conversion results to wrong output like
869 // foo,bar@example,com or fooÙ«bar@exampleÙ«com.
870 // Also some 'numeric' things like Scene codes
871 // are included here as we really don't want
872 // commas inserted.
873 case 'ImageDescription':
874 case 'UserComment':
875 case 'Artist':
876 case 'Copyright':
877 case 'RelatedSoundFile':
878 case 'ImageUniqueID':
879 case 'SpectralSensitivity':
880 case 'GPSSatellites':
881 case 'GPSVersionID':
882 case 'GPSMapDatum':
883 case 'Keywords':
884 case 'WorldRegionDest':
885 case 'CountryDest':
886 case 'CountryCodeDest':
887 case 'ProvinceOrStateDest':
888 case 'CityDest':
889 case 'SublocationDest':
890 case 'WorldRegionCreated':
891 case 'CountryCreated':
892 case 'CountryCodeCreated':
893 case 'ProvinceOrStateCreated':
894 case 'CityCreated':
895 case 'SublocationCreated':
896 case 'ObjectName':
897 case 'SpecialInstructions':
898 case 'Headline':
899 case 'Credit':
900 case 'Source':
901 case 'EditStatus':
902 case 'FixtureIdentifier':
903 case 'LocationDest':
904 case 'LocationDestCode':
905 case 'Writer':
906 case 'JPEGFileComment':
907 case 'iimSupplementalCategory':
908 case 'OriginalTransmissionRef':
909 case 'Identifier':
910 case 'dc-contributor':
911 case 'dc-coverage':
912 case 'dc-publisher':
913 case 'dc-relation':
914 case 'dc-rights':
915 case 'dc-source':
916 case 'dc-type':
917 case 'Lens':
918 case 'SerialNumber':
919 case 'CameraOwnerName':
920 case 'Label':
921 case 'Nickname':
922 case 'RightsCertificate':
923 case 'CopyrightOwner':
924 case 'UsageTerms':
925 case 'WebStatement':
926 case 'OriginalDocumentID':
927 case 'LicenseUrl':
928 case 'MorePermissionsUrl':
929 case 'AttributionUrl':
930 case 'PreferredAttributionName':
931 case 'PNGFileComment':
932 case 'Disclaimer':
933 case 'ContentWarning':
934 case 'GIFFileComment':
935 case 'SceneCode':
936 case 'IntellectualGenre':
937 case 'Event':
938 case 'OrginisationInImage':
939 case 'PersonInImage':
940
941 $val = htmlspecialchars( $val );
942 break;
943
944 case 'ObjectCycle':
945 switch ( $val ) {
946 case 'a':
947 case 'p':
948 case 'b':
949 $val = $this->exifMsg( $tag, $val );
950 break;
951 default:
952 $val = htmlspecialchars( $val );
953 break;
954 }
955 break;
956 case 'Copyrighted':
957 switch ( $val ) {
958 case 'True':
959 case 'False':
960 $val = $this->exifMsg( $tag, $val );
961 break;
962 }
963 break;
964 case 'Rating':
965 if ( $val == '-1' ) {
966 $val = $this->exifMsg( $tag, 'rejected' );
967 } else {
968 $val = $this->formatNum( $val );
969 }
970 break;
971
972 case 'LanguageCode':
973 $lang = Language::fetchLanguageName( strtolower( $val ), $this->getLanguage()->getCode() );
974 $val = htmlspecialchars( $lang ?: $val );
975 break;
976
977 default:
978 $val = $this->formatNum( $val );
979 break;
980 }
981 }
982 // End formatting values, start flattening arrays.
983 $vals = $this->flattenArrayReal( $vals, $type );
984 }
985
986 return $tags;
987 }
988
1003 public static function flattenArrayContentLang( $vals, $type = 'ul',
1004 $noHtml = false, $context = false
1005 ) {
1006 $obj = new FormatMetadata;
1007 if ( $context ) {
1008 $obj->setContext( $context );
1009 }
1010 $context = new DerivativeContext( $obj->getContext() );
1011 $context->setLanguage( MediaWikiServices::getInstance()->getContentLanguage() );
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 ContentLanguage,
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 ) {
1220 if ( $val === '' ) {
1221 $val = 'value';
1222 }
1223
1224 return $this->msg(
1225 MediaWikiServices::getInstance()->getContentLanguage()->lc( "exif-$tag-$val" ),
1226 $arg,
1227 $arg2
1228 )->text();
1229 }
1230
1239 private function formatNum( $num, $round = false ) {
1240 $m = [];
1241 if ( is_array( $num ) ) {
1242 $out = [];
1243 foreach ( $num as $number ) {
1244 $out[] = $this->formatNum( $number );
1245 }
1246
1247 return $this->getLanguage()->commaList( $out );
1248 }
1249 if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
1250 if ( $m[2] != 0 ) {
1251 $newNum = $m[1] / $m[2];
1252 if ( $round !== false ) {
1253 $newNum = round( $newNum, $round );
1254 }
1255 } else {
1256 $newNum = $num;
1257 }
1258
1259 return $this->getLanguage()->formatNum( $newNum );
1260 } else {
1261 if ( is_numeric( $num ) && $round !== false ) {
1262 $num = round( $num, $round );
1263 }
1264
1265 return $this->getLanguage()->formatNum( $num );
1266 }
1267 }
1268
1275 private function formatFraction( $num ) {
1276 $m = [];
1277 if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
1278 $numerator = intval( $m[1] );
1279 $denominator = intval( $m[2] );
1280 $gcd = $this->gcd( abs( $numerator ), $denominator );
1281 if ( $gcd != 0 ) {
1282 // 0 shouldn't happen! ;)
1283 return $this->formatNum( $numerator / $gcd ) . '/' . $this->formatNum( $denominator / $gcd );
1284 }
1285 }
1286
1287 return $this->formatNum( $num );
1288 }
1289
1297 private function gcd( $a, $b ) {
1298 /*
1299 // https://en.wikipedia.org/wiki/Euclidean_algorithm
1300 // Recursive form would be:
1301 if( $b == 0 )
1302 return $a;
1303 else
1304 return gcd( $b, $a % $b );
1305 */
1306 while ( $b != 0 ) {
1307 $remainder = $a % $b;
1308
1309 // tail recursion...
1310 $a = $b;
1311 $b = $remainder;
1312 }
1313
1314 return $a;
1315 }
1316
1329 private function convertNewsCode( $val ) {
1330 if ( !preg_match( '/^\d{8}$/D', $val ) ) {
1331 // Not a valid news code.
1332 return $val;
1333 }
1334 $cat = '';
1335 switch ( substr( $val, 0, 2 ) ) {
1336 case '01':
1337 $cat = 'ace';
1338 break;
1339 case '02':
1340 $cat = 'clj';
1341 break;
1342 case '03':
1343 $cat = 'dis';
1344 break;
1345 case '04':
1346 $cat = 'fin';
1347 break;
1348 case '05':
1349 $cat = 'edu';
1350 break;
1351 case '06':
1352 $cat = 'evn';
1353 break;
1354 case '07':
1355 $cat = 'hth';
1356 break;
1357 case '08':
1358 $cat = 'hum';
1359 break;
1360 case '09':
1361 $cat = 'lab';
1362 break;
1363 case '10':
1364 $cat = 'lif';
1365 break;
1366 case '11':
1367 $cat = 'pol';
1368 break;
1369 case '12':
1370 $cat = 'rel';
1371 break;
1372 case '13':
1373 $cat = 'sci';
1374 break;
1375 case '14':
1376 $cat = 'soi';
1377 break;
1378 case '15':
1379 $cat = 'spo';
1380 break;
1381 case '16':
1382 $cat = 'war';
1383 break;
1384 case '17':
1385 $cat = 'wea';
1386 break;
1387 }
1388 if ( $cat !== '' ) {
1389 $catMsg = $this->exifMsg( 'iimcategory', $cat );
1390 $val = $this->exifMsg( 'subjectnewscode', '', $val, $catMsg );
1391 }
1392
1393 return $val;
1394 }
1395
1404 private function formatCoords( $coord, $type ) {
1405 if ( !is_numeric( $coord ) ) {
1406 wfDebugLog( 'exif', __METHOD__ . ": \"$coord\" is not a number" );
1407 return (string)$coord;
1408 }
1409
1410 $ref = '';
1411 if ( $coord < 0 ) {
1412 $nCoord = -$coord;
1413 if ( $type === 'latitude' ) {
1414 $ref = 'S';
1415 } elseif ( $type === 'longitude' ) {
1416 $ref = 'W';
1417 }
1418 } else {
1419 $nCoord = (float)$coord;
1420 if ( $type === 'latitude' ) {
1421 $ref = 'N';
1422 } elseif ( $type === 'longitude' ) {
1423 $ref = 'E';
1424 }
1425 }
1426
1427 $deg = floor( $nCoord );
1428 $min = floor( ( $nCoord - $deg ) * 60 );
1429 $sec = round( ( ( $nCoord - $deg ) * 60 - $min ) * 60, 2 );
1430
1431 $deg = $this->formatNum( $deg );
1432 $min = $this->formatNum( $min );
1433 $sec = $this->formatNum( $sec );
1434
1435 // Note the default message "$1° $2′ $3″ $4" ignores the 5th parameter
1436 return $this->msg( 'exif-coordinate-format', $deg, $min, $sec, $ref, $coord )->text();
1437 }
1438
1453 public function collapseContactInfo( $vals ) {
1454 if ( !( isset( $vals['CiAdrExtadr'] )
1455 || isset( $vals['CiAdrCity'] )
1456 || isset( $vals['CiAdrCtry'] )
1457 || isset( $vals['CiEmailWork'] )
1458 || isset( $vals['CiTelWork'] )
1459 || isset( $vals['CiAdrPcode'] )
1460 || isset( $vals['CiAdrRegion'] )
1461 || isset( $vals['CiUrlWork'] )
1462 ) ) {
1463 // We don't have any sub-properties
1464 // This could happen if its using old
1465 // iptc that just had this as a free-form
1466 // text value.
1467 // Note: We run this through htmlspecialchars
1468 // partially to be consistent, and partially
1469 // because people often insert >, etc into
1470 // the metadata which should not be interpreted
1471 // but we still want to auto-link urls.
1472 foreach ( $vals as &$val ) {
1473 $val = htmlspecialchars( $val );
1474 }
1475
1476 return $this->flattenArrayReal( $vals );
1477 } else {
1478 // We have a real ContactInfo field.
1479 // Its unclear if all these fields have to be
1480 // set, so assume they do not.
1481 $url = $tel = $street = $city = $country = '';
1482 $email = $postal = $region = '';
1483
1484 // Also note, some of the class names this uses
1485 // are similar to those used by hCard. This is
1486 // mostly because they're sensible names. This
1487 // does not (and does not attempt to) output
1488 // stuff in the hCard microformat. However it
1489 // might output in the adr microformat.
1490
1491 if ( isset( $vals['CiAdrExtadr'] ) ) {
1492 // Todo: This can potentially be multi-line.
1493 // Need to check how that works in XMP.
1494 $street = '<span class="extended-address">'
1495 . htmlspecialchars(
1496 $vals['CiAdrExtadr'] )
1497 . '</span>';
1498 }
1499 if ( isset( $vals['CiAdrCity'] ) ) {
1500 $city = '<span class="locality">'
1501 . htmlspecialchars( $vals['CiAdrCity'] )
1502 . '</span>';
1503 }
1504 if ( isset( $vals['CiAdrCtry'] ) ) {
1505 $country = '<span class="country-name">'
1506 . htmlspecialchars( $vals['CiAdrCtry'] )
1507 . '</span>';
1508 }
1509 if ( isset( $vals['CiEmailWork'] ) ) {
1510 $emails = [];
1511 // Have to split multiple emails at commas/new lines.
1512 $splitEmails = explode( "\n", $vals['CiEmailWork'] );
1513 foreach ( $splitEmails as $e1 ) {
1514 // Also split on comma
1515 foreach ( explode( ',', $e1 ) as $e2 ) {
1516 $finalEmail = trim( $e2 );
1517 if ( $finalEmail == ',' || $finalEmail == '' ) {
1518 continue;
1519 }
1520 if ( strpos( $finalEmail, '<' ) !== false ) {
1521 // Don't do fancy formatting to
1522 // "My name" <foo@bar.com> style stuff
1523 $emails[] = $finalEmail;
1524 } else {
1525 $emails[] = '[mailto:'
1526 . $finalEmail
1527 . ' <span class="email">'
1528 . $finalEmail
1529 . '</span>]';
1530 }
1531 }
1532 }
1533 $email = implode( ', ', $emails );
1534 }
1535 if ( isset( $vals['CiTelWork'] ) ) {
1536 $tel = '<span class="tel">'
1537 . htmlspecialchars( $vals['CiTelWork'] )
1538 . '</span>';
1539 }
1540 if ( isset( $vals['CiAdrPcode'] ) ) {
1541 $postal = '<span class="postal-code">'
1542 . htmlspecialchars(
1543 $vals['CiAdrPcode'] )
1544 . '</span>';
1545 }
1546 if ( isset( $vals['CiAdrRegion'] ) ) {
1547 // Note this is province/state.
1548 $region = '<span class="region">'
1549 . htmlspecialchars(
1550 $vals['CiAdrRegion'] )
1551 . '</span>';
1552 }
1553 if ( isset( $vals['CiUrlWork'] ) ) {
1554 $url = '<span class="url">'
1555 . htmlspecialchars( $vals['CiUrlWork'] )
1556 . '</span>';
1557 }
1558
1559 return $this->msg( 'exif-contact-value', $email, $url,
1560 $street, $city, $region, $postal, $country,
1561 $tel )->text();
1562 }
1563 }
1564
1571 public static function getVisibleFields() {
1572 $fields = [];
1573 $lines = explode( "\n", wfMessage( 'metadata-fields' )->inContentLanguage()->text() );
1574 foreach ( $lines as $line ) {
1575 $matches = [];
1576 if ( preg_match( '/^\\*\s*(.*?)\s*$/', $line, $matches ) ) {
1577 $fields[] = $matches[1];
1578 }
1579 }
1580 $fields = array_map( 'strtolower', $fields );
1581
1582 return $fields;
1583 }
1584
1592 public function fetchExtendedMetadata( File $file ) {
1593 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1594
1595 // If revision deleted, exit immediately
1596 if ( $file->isDeleted( File::DELETED_FILE ) ) {
1597 return [];
1598 }
1599
1600 $cacheKey = $cache->makeKey(
1601 'getExtendedMetadata',
1602 $this->getLanguage()->getCode(),
1603 (int)$this->singleLang,
1604 $file->getSha1()
1605 );
1606
1607 $cachedValue = $cache->get( $cacheKey );
1608 if (
1609 $cachedValue
1610 && Hooks::run( 'ValidateExtendedMetadataCache', [ $cachedValue['timestamp'], $file ] )
1611 ) {
1612 $extendedMetadata = $cachedValue['data'];
1613 } else {
1614 $maxCacheTime = ( $file instanceof ForeignAPIFile ) ? 60 * 60 * 12 : 60 * 60 * 24 * 30;
1615 $fileMetadata = $this->getExtendedMetadataFromFile( $file );
1616 $extendedMetadata = $this->getExtendedMetadataFromHook( $file, $fileMetadata, $maxCacheTime );
1617 if ( $this->singleLang ) {
1618 $this->resolveMultilangMetadata( $extendedMetadata );
1619 }
1620 $this->discardMultipleValues( $extendedMetadata );
1621 // Make sure the metadata won't break the API when an XML format is used.
1622 // This is an API-specific function so it would be cleaner to call it from
1623 // outside fetchExtendedMetadata, but this way we don't need to redo the
1624 // computation on a cache hit.
1625 $this->sanitizeArrayForAPI( $extendedMetadata );
1626 $valueToCache = [ 'data' => $extendedMetadata, 'timestamp' => wfTimestampNow() ];
1627 $cache->set( $cacheKey, $valueToCache, $maxCacheTime );
1628 }
1629
1630 return $extendedMetadata;
1631 }
1632
1642 protected function getExtendedMetadataFromFile( File $file ) {
1643 // If this is a remote file accessed via an API request, we already
1644 // have remote metadata so we just ignore any local one
1645 if ( $file instanceof ForeignAPIFile ) {
1646 // In case of error we pretend no metadata - this will get cached.
1647 // Might or might not be a good idea.
1648 return $file->getExtendedMetadata() ?: [];
1649 }
1650
1651 $uploadDate = wfTimestamp( TS_ISO_8601, $file->getTimestamp() );
1652
1653 $fileMetadata = [
1654 // This is modification time, which is close to "upload" time.
1655 'DateTime' => [
1656 'value' => $uploadDate,
1657 'source' => 'mediawiki-metadata',
1658 ],
1659 ];
1660
1661 $title = $file->getTitle();
1662 if ( $title ) {
1663 $text = $title->getText();
1664 $pos = strrpos( $text, '.' );
1665
1666 if ( $pos ) {
1667 $name = substr( $text, 0, $pos );
1668 } else {
1669 $name = $text;
1670 }
1671
1672 $fileMetadata['ObjectName'] = [
1673 'value' => $name,
1674 'source' => 'mediawiki-metadata',
1675 ];
1676 }
1677
1678 return $fileMetadata;
1679 }
1680
1691 protected function getExtendedMetadataFromHook( File $file, array $extendedMetadata,
1692 &$maxCacheTime
1693 ) {
1694 Hooks::run( 'GetExtendedMetadata', [
1695 &$extendedMetadata,
1696 $file,
1697 $this->getContext(),
1698 $this->singleLang,
1699 &$maxCacheTime
1700 ] );
1701
1702 $visible = array_flip( self::getVisibleFields() );
1703 foreach ( $extendedMetadata as $key => $value ) {
1704 if ( !isset( $visible[strtolower( $key )] ) ) {
1705 $extendedMetadata[$key]['hidden'] = '';
1706 }
1707 }
1708
1709 return $extendedMetadata;
1710 }
1711
1720 protected function resolveMultilangValue( $value ) {
1721 if (
1722 !is_array( $value )
1723 || !isset( $value['_type'] )
1724 || $value['_type'] != 'lang'
1725 ) {
1726 return $value; // do nothing if not a multilang array
1727 }
1728
1729 // choose the language best matching user or site settings
1730 $priorityLanguages = $this->getPriorityLanguages();
1731 foreach ( $priorityLanguages as $lang ) {
1732 if ( isset( $value[$lang] ) ) {
1733 return $value[$lang];
1734 }
1735 }
1736
1737 // otherwise go with the default language, if set
1738 if ( isset( $value['x-default'] ) ) {
1739 return $value['x-default'];
1740 }
1741
1742 // otherwise just return any one language
1743 unset( $value['_type'] );
1744 if ( !empty( $value ) ) {
1745 return reset( $value );
1746 }
1747
1748 // this should not happen; signal error
1749 return null;
1750 }
1751
1761 protected function resolveMultivalueValue( $value ) {
1762 if ( !is_array( $value ) ) {
1763 return $value;
1764 } elseif ( isset( $value['_type'] ) && $value['_type'] === 'lang' ) {
1765 // if this is a multilang array, process fields separately
1766 $newValue = [];
1767 foreach ( $value as $k => $v ) {
1768 $newValue[$k] = $this->resolveMultivalueValue( $v );
1769 }
1770 return $newValue;
1771 } else { // _type is 'ul' or 'ol' or missing in which case it defaults to 'ul'
1772 $v = reset( $value );
1773 if ( key( $value ) === '_type' ) {
1774 $v = next( $value );
1775 }
1776 return $v;
1777 }
1778 }
1779
1786 protected function resolveMultilangMetadata( &$metadata ) {
1787 if ( !is_array( $metadata ) ) {
1788 return;
1789 }
1790 foreach ( $metadata as &$field ) {
1791 if ( isset( $field['value'] ) ) {
1792 $field['value'] = $this->resolveMultilangValue( $field['value'] );
1793 }
1794 }
1795 }
1796
1803 protected function discardMultipleValues( &$metadata ) {
1804 if ( !is_array( $metadata ) ) {
1805 return;
1806 }
1807 foreach ( $metadata as $key => &$field ) {
1808 if ( $key === 'Software' || $key === 'Contact' ) {
1809 // we skip some fields which have composite values. They are not particularly interesting
1810 // and you can get them via the metadata / commonmetadata APIs anyway.
1811 continue;
1812 }
1813 if ( isset( $field['value'] ) ) {
1814 $field['value'] = $this->resolveMultivalueValue( $field['value'] );
1815 }
1816 }
1817 }
1818
1823 protected function sanitizeArrayForAPI( &$arr ) {
1824 if ( !is_array( $arr ) ) {
1825 return;
1826 }
1827
1828 $counter = 1;
1829 foreach ( $arr as $key => &$value ) {
1830 $sanitizedKey = $this->sanitizeKeyForAPI( $key );
1831 if ( $sanitizedKey !== $key ) {
1832 if ( isset( $arr[$sanitizedKey] ) ) {
1833 // Make the sanitized keys hopefully unique.
1834 // To make it definitely unique would be too much effort, given that
1835 // sanitizing is only needed for misformatted metadata anyway, but
1836 // this at least covers the case when $arr is numeric.
1837 $sanitizedKey .= $counter;
1838 ++$counter;
1839 }
1840 $arr[$sanitizedKey] = $arr[$key];
1841 unset( $arr[$key] );
1842 }
1843 if ( is_array( $value ) ) {
1844 $this->sanitizeArrayForAPI( $value );
1845 }
1846 }
1847
1848 // Handle API metadata keys (particularly "_type")
1849 $keys = array_filter( array_keys( $arr ), 'ApiResult::isMetadataKey' );
1850 if ( $keys ) {
1852 }
1853 }
1854
1861 protected function sanitizeKeyForAPI( $key ) {
1862 // drop all characters which are not valid in an XML tag name
1863 // a bunch of non-ASCII letters would be valid but probably won't
1864 // be used so we take the easy way
1865 $key = preg_replace( '/[^a-zA-z0-9_:.\-]/', '', $key );
1866 // drop characters which are invalid at the first position
1867 $key = preg_replace( '/^[\d\-.]+/', '', $key );
1868
1869 if ( $key == '' ) {
1870 $key = '_';
1871 }
1872
1873 // special case for an internal keyword
1874 if ( $key == '_element' ) {
1875 $key = 'element';
1876 }
1877
1878 return $key;
1879 }
1880
1887 protected function getPriorityLanguages() {
1888 $priorityLanguages =
1889 Language::getFallbacksIncludingSiteLanguage( $this->getLanguage()->getCode() );
1890 $priorityLanguages = array_merge(
1891 (array)$this->getLanguage()->getCode(),
1892 $priorityLanguages[0],
1893 $priorityLanguages[1]
1894 );
1895
1896 return $priorityLanguages;
1897 }
1898}
This list may contain false positives That usually means there is additional text with links below the first Each row contains links to the first and second as well as the first line of the second redirect text
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.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
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( $key)
Get a Message object with context set Parameters are the same as wfMessage()
IContextSource $context
getContext()
Get the base IContextSource object.
setContext(IContextSource $context)
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
const DELETED_FILE
Definition File.php:53
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
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition hooks.txt:1841
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 use $formDescriptor instead 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 key
Definition hooks.txt:2214
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 use $formDescriptor instead 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:894
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:2226
$cache
Definition mcc.php:33
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
$content
$lines
Definition router.php:61
if(!isset( $args[0])) $lang