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