MediaWiki REL1_28
XMP.php
Go to the documentation of this file.
1<?php
24use Psr\Log\LoggerAwareInterface;
25use Psr\Log\LoggerInterface;
26use Psr\Log\NullLogger;
27use Wikimedia\ScopedCallback;
28
54class XMPReader implements LoggerAwareInterface {
56 protected $items;
57
59 private $curItem = [];
60
62 private $ancestorStruct = false;
63
65 private $charContent = false;
66
68 private $mode = [];
69
71 private $results = [];
72
74 private $processingArray = false;
75
77 private $itemLang = false;
78
80 private $xmlParser;
81
83 private $charset = false;
84
86 private $extendedXMPOffset = 0;
87
89 private $parsable = 0;
90
92 private $xmlParsableBuffer = '';
93
103 const MODE_INITIAL = 0;
104 const MODE_IGNORE = 1;
105 const MODE_LI = 2;
106 const MODE_LI_LANG = 3;
107 const MODE_QDESC = 4;
108
109 // The following MODE constants are also used in the
110 // $items array to denote what type of property the item is.
111 const MODE_SIMPLE = 10;
112 const MODE_STRUCT = 11; // structure (associative array)
113 const MODE_SEQ = 12; // ordered list
114 const MODE_BAG = 13; // unordered list
115 const MODE_LANG = 14;
116 const MODE_ALT = 15; // non-language alt. Currently not implemented, and not needed atm.
117 const MODE_BAGSTRUCT = 16; // A BAG of Structs.
118
119 const NS_RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
120 const NS_XML = 'http://www.w3.org/XML/1998/namespace';
121
122 // States used while determining if XML is safe to parse
124 const PARSABLE_OK = 1;
126 const PARSABLE_NO = 3;
127
131 private $logger;
132
138 function __construct( LoggerInterface $logger = null ) {
139
140 if ( !function_exists( 'xml_parser_create_ns' ) ) {
141 // this should already be checked by this point
142 throw new RuntimeException( 'XMP support requires XML Parser' );
143 }
144 if ( $logger ) {
145 $this->setLogger( $logger );
146 } else {
147 $this->setLogger( new NullLogger() );
148 }
149
150 $this->items = XMPInfo::getItems();
151
152 $this->resetXMLParser();
153 }
154
155 public function setLogger( LoggerInterface $logger ) {
156 $this->logger = $logger;
157 }
158
166 private function destroyXMLParser() {
167 if ( $this->xmlParser ) {
168 xml_parser_free( $this->xmlParser );
169 $this->xmlParser = null;
170 }
171 }
172
177 private function resetXMLParser() {
178
179 $this->destroyXMLParser();
180
181 $this->xmlParser = xml_parser_create_ns( 'UTF-8', ' ' );
182 xml_parser_set_option( $this->xmlParser, XML_OPTION_CASE_FOLDING, 0 );
183 xml_parser_set_option( $this->xmlParser, XML_OPTION_SKIP_WHITE, 1 );
184
185 xml_set_element_handler( $this->xmlParser,
186 [ $this, 'startElement' ],
187 [ $this, 'endElement' ] );
188
189 xml_set_character_data_handler( $this->xmlParser, [ $this, 'char' ] );
190
191 $this->parsable = self::PARSABLE_UNKNOWN;
192 $this->xmlParsableBuffer = '';
193 }
194
198 public static function isSupported() {
199 return function_exists( 'xml_parser_create_ns' ) && class_exists( 'XMLReader' );
200 }
201
208 public function getResults() {
209 // xmp-special is for metadata that affects how stuff
210 // is extracted. For example xmpNote:HasExtendedXMP.
211
212 // It is also used to handle photoshop:AuthorsPosition
213 // which is weird and really part of another property,
214 // see 2:85 in IPTC. See also pg 21 of IPTC4XMP standard.
215 // The location fields also use it.
216
217 $data = $this->results;
218
219 if ( isset( $data['xmp-special']['AuthorsPosition'] )
220 && is_string( $data['xmp-special']['AuthorsPosition'] )
221 && isset( $data['xmp-general']['Artist'][0] )
222 ) {
223 // Note, if there is more than one creator,
224 // this only applies to first. This also will
225 // only apply to the dc:Creator prop, not the
226 // exif:Artist prop.
227
228 $data['xmp-general']['Artist'][0] =
229 $data['xmp-special']['AuthorsPosition'] . ', '
230 . $data['xmp-general']['Artist'][0];
231 }
232
233 // Go through the LocationShown and LocationCreated
234 // changing it to the non-hierarchal form used by
235 // the other location fields.
236
237 if ( isset( $data['xmp-special']['LocationShown'][0] )
238 && is_array( $data['xmp-special']['LocationShown'][0] )
239 ) {
240 // the is_array is just paranoia. It should always
241 // be an array.
242 foreach ( $data['xmp-special']['LocationShown'] as $loc ) {
243 if ( !is_array( $loc ) ) {
244 // To avoid copying over the _type meta-fields.
245 continue;
246 }
247 foreach ( $loc as $field => $val ) {
248 $data['xmp-general'][$field . 'Dest'][] = $val;
249 }
250 }
251 }
252 if ( isset( $data['xmp-special']['LocationCreated'][0] )
253 && is_array( $data['xmp-special']['LocationCreated'][0] )
254 ) {
255 // the is_array is just paranoia. It should always
256 // be an array.
257 foreach ( $data['xmp-special']['LocationCreated'] as $loc ) {
258 if ( !is_array( $loc ) ) {
259 // To avoid copying over the _type meta-fields.
260 continue;
261 }
262 foreach ( $loc as $field => $val ) {
263 $data['xmp-general'][$field . 'Created'][] = $val;
264 }
265 }
266 }
267
268 // We don't want to return the special values, since they're
269 // special and not info to be stored about the file.
270 unset( $data['xmp-special'] );
271
272 // Convert GPSAltitude to negative if below sea level.
273 if ( isset( $data['xmp-exif']['GPSAltitudeRef'] )
274 && isset( $data['xmp-exif']['GPSAltitude'] )
275 ) {
276
277 // Must convert to a real before multiplying by -1
278 // XMPValidate guarantees there will always be a '/' in this value.
279 list( $nom, $denom ) = explode( '/', $data['xmp-exif']['GPSAltitude'] );
280 $data['xmp-exif']['GPSAltitude'] = $nom / $denom;
281
282 if ( $data['xmp-exif']['GPSAltitudeRef'] == '1' ) {
283 $data['xmp-exif']['GPSAltitude'] *= -1;
284 }
285 unset( $data['xmp-exif']['GPSAltitudeRef'] );
286 }
287
288 return $data;
289 }
290
303 public function parse( $content, $allOfIt = true ) {
304 if ( !$this->xmlParser ) {
305 $this->resetXMLParser();
306 }
307 try {
308
309 // detect encoding by looking for BOM which is supposed to be in processing instruction.
310 // see page 12 of http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart3.pdf
311 if ( !$this->charset ) {
312 $bom = [];
313 if ( preg_match( '/\xEF\xBB\xBF|\xFE\xFF|\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\xFF\xFE/',
314 $content, $bom )
315 ) {
316 switch ( $bom[0] ) {
317 case "\xFE\xFF":
318 $this->charset = 'UTF-16BE';
319 break;
320 case "\xFF\xFE":
321 $this->charset = 'UTF-16LE';
322 break;
323 case "\x00\x00\xFE\xFF":
324 $this->charset = 'UTF-32BE';
325 break;
326 case "\xFF\xFE\x00\x00":
327 $this->charset = 'UTF-32LE';
328 break;
329 case "\xEF\xBB\xBF":
330 $this->charset = 'UTF-8';
331 break;
332 default:
333 // this should be impossible to get to
334 throw new RuntimeException( "Invalid BOM" );
335 }
336 } else {
337 // standard specifically says, if no bom assume utf-8
338 $this->charset = 'UTF-8';
339 }
340 }
341 if ( $this->charset !== 'UTF-8' ) {
342 // don't convert if already utf-8
343 MediaWiki\suppressWarnings();
344 $content = iconv( $this->charset, 'UTF-8//IGNORE', $content );
345 MediaWiki\restoreWarnings();
346 }
347
348 // Ensure the XMP block does not have an xml doctype declaration, which
349 // could declare entities unsafe to parse with xml_parse (T85848/T71210).
350 if ( $this->parsable !== self::PARSABLE_OK ) {
351 if ( $this->parsable === self::PARSABLE_NO ) {
352 throw new RuntimeException( 'Unsafe doctype declaration in XML.' );
353 }
354
355 $content = $this->xmlParsableBuffer . $content;
356 if ( !$this->checkParseSafety( $content ) ) {
357 if ( !$allOfIt && $this->parsable !== self::PARSABLE_NO ) {
358 // parse wasn't Unsuccessful yet, so return true
359 // in this case.
360 return true;
361 }
362 $msg = ( $this->parsable === self::PARSABLE_NO ) ?
363 'Unsafe doctype declaration in XML.' :
364 'No root element found in XML.';
365 throw new RuntimeException( $msg );
366 }
367 }
368
369 $ok = xml_parse( $this->xmlParser, $content, $allOfIt );
370 if ( !$ok ) {
371 $code = xml_get_error_code( $this->xmlParser );
372 $error = xml_error_string( $code );
373 $line = xml_get_current_line_number( $this->xmlParser );
374 $col = xml_get_current_column_number( $this->xmlParser );
375 $offset = xml_get_current_byte_index( $this->xmlParser );
376
377 $this->logger->warning(
378 '{method} : Error reading XMP content: {error} ' .
379 '(line: {line} column: {column} byte offset: {offset})',
380 [
381 'method' => __METHOD__,
382 'error_code' => $code,
383 'error' => $error,
384 'line' => $line,
385 'column' => $col,
386 'offset' => $offset,
387 'content' => $content,
388 ] );
389 $this->results = []; // blank if error.
390 $this->destroyXMLParser();
391 return false;
392 }
393 } catch ( Exception $e ) {
394 $this->logger->warning(
395 '{method} Exception caught while parsing: ' . $e->getMessage(),
396 [
397 'method' => __METHOD__,
398 'exception' => $e,
399 'content' => $content,
400 ]
401 );
402 $this->results = [];
403 return false;
404 }
405 if ( $allOfIt ) {
406 $this->destroyXMLParser();
407 }
408
409 return true;
410 }
411
419 public function parseExtended( $content ) {
420 // @todo FIXME: This is untested. Hard to find example files
421 // or programs that make such files..
422 $guid = substr( $content, 0, 32 );
423 if ( !isset( $this->results['xmp-special']['HasExtendedXMP'] )
424 || $this->results['xmp-special']['HasExtendedXMP'] !== $guid
425 ) {
426 $this->logger->info( __METHOD__ .
427 " Ignoring XMPExtended block due to wrong guid (guid= '$guid')" );
428
429 return false;
430 }
431 $len = unpack( 'Nlength/Noffset', substr( $content, 32, 8 ) );
432
433 if ( !$len ||
434 $len['length'] < 4 ||
435 $len['offset'] < 0 ||
436 $len['offset'] > $len['length']
437 ) {
438 $this->logger->info(
439 __METHOD__ . 'Error reading extended XMP block, invalid length or offset.'
440 );
441
442 return false;
443 }
444
445 // we're not very robust here. we should accept it in the wrong order.
446 // To quote the XMP standard:
447 // "A JPEG writer should write the ExtendedXMP marker segments in order,
448 // immediately following the StandardXMP. However, the JPEG standard
449 // does not require preservation of marker segment order. A robust JPEG
450 // reader should tolerate the marker segments in any order."
451 // On the other hand, the probability that an image will have more than
452 // 128k of metadata is rather low... so the probability that it will have
453 // > 128k, and be in the wrong order is very low...
454
455 if ( $len['offset'] !== $this->extendedXMPOffset ) {
456 $this->logger->info( __METHOD__ . 'Ignoring XMPExtended block due to wrong order. (Offset was '
457 . $len['offset'] . ' but expected ' . $this->extendedXMPOffset . ')' );
458
459 return false;
460 }
461
462 if ( $len['offset'] === 0 ) {
463 // if we're starting the extended block, we've probably already
464 // done the XMPStandard block, so reset.
465 $this->resetXMLParser();
466 }
467
468 $this->extendedXMPOffset += $len['length'];
469
470 $actualContent = substr( $content, 40 );
471
472 if ( $this->extendedXMPOffset === strlen( $actualContent ) ) {
473 $atEnd = true;
474 } else {
475 $atEnd = false;
476 }
477
478 $this->logger->debug( __METHOD__ . 'Parsing a XMPExtended block' );
479
480 return $this->parse( $actualContent, $atEnd );
481 }
482
499 function char( $parser, $data ) {
500
501 $data = trim( $data );
502 if ( trim( $data ) === "" ) {
503 return;
504 }
505
506 if ( !isset( $this->mode[0] ) ) {
507 throw new RuntimeException( 'Unexpected character data before first rdf:Description element' );
508 }
509
510 if ( $this->mode[0] === self::MODE_IGNORE ) {
511 return;
512 }
513
514 if ( $this->mode[0] !== self::MODE_SIMPLE
515 && $this->mode[0] !== self::MODE_QDESC
516 ) {
517 throw new RuntimeException( 'character data where not expected. (mode ' . $this->mode[0] . ')' );
518 }
519
520 // to check, how does this handle w.s.
521 if ( $this->charContent === false ) {
522 $this->charContent = $data;
523 } else {
524 $this->charContent .= $data;
525 }
526 }
527
536 private function checkParseSafety( $content ) {
537 $reader = new XMLReader();
538 $result = null;
539
540 // For XMLReader to parse incomplete/invalid XML, it has to be open()'ed
541 // instead of using XML().
542 $reader->open(
543 'data://text/plain,' . urlencode( $content ),
544 null,
545 LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_NONET
546 );
547
548 $oldDisable = libxml_disable_entity_loader( true );
550 $reset = new ScopedCallback(
551 'libxml_disable_entity_loader',
552 [ $oldDisable ]
553 );
554 $reader->setParserProperty( XMLReader::SUBST_ENTITIES, false );
555
556 // Even with LIBXML_NOWARNING set, XMLReader::read gives a warning
557 // when parsing truncated XML, which causes unit tests to fail.
558 MediaWiki\suppressWarnings();
559 while ( $reader->read() ) {
560 if ( $reader->nodeType === XMLReader::ELEMENT ) {
561 // Reached the first element without hitting a doctype declaration
562 $this->parsable = self::PARSABLE_OK;
563 $result = true;
564 break;
565 }
566 if ( $reader->nodeType === XMLReader::DOC_TYPE ) {
567 $this->parsable = self::PARSABLE_NO;
568 $result = false;
569 break;
570 }
571 }
572 MediaWiki\restoreWarnings();
573
574 if ( !is_null( $result ) ) {
575 return $result;
576 }
577
578 // Reached the end of the parsable xml without finding an element
579 // or doctype. Buffer and try again.
580 $this->parsable = self::PARSABLE_BUFFERING;
581 $this->xmlParsableBuffer = $content;
582 return false;
583 }
584
591 private function endElementModeIgnore( $elm ) {
592 if ( $this->curItem[0] === $elm ) {
593 array_shift( $this->curItem );
594 array_shift( $this->mode );
595 }
596 }
597
613 private function endElementModeSimple( $elm ) {
614 if ( $this->charContent !== false ) {
615 if ( $this->processingArray ) {
616 // if we're processing an array, use the original element
617 // name instead of rdf:li.
618 list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
619 } else {
620 list( $ns, $tag ) = explode( ' ', $elm, 2 );
621 }
622 $this->saveValue( $ns, $tag, $this->charContent );
623
624 $this->charContent = false; // reset
625 }
626 array_shift( $this->curItem );
627 array_shift( $this->mode );
628 }
629
648 private function endElementNested( $elm ) {
649
650 /* cur item must be the same as $elm, unless if in MODE_STRUCT
651 in which case it could also be rdf:Description */
652 if ( $this->curItem[0] !== $elm
653 && !( $elm === self::NS_RDF . ' Description'
654 && $this->mode[0] === self::MODE_STRUCT )
655 ) {
656 throw new RuntimeException( "nesting mismatch. got a </$elm> but expected a </" .
657 $this->curItem[0] . '>' );
658 }
659
660 // Validate structures.
661 list( $ns, $tag ) = explode( ' ', $elm, 2 );
662 if ( isset( $this->items[$ns][$tag]['validate'] ) ) {
663 $info =& $this->items[$ns][$tag];
664 $finalName = isset( $info['map_name'] )
665 ? $info['map_name'] : $tag;
666
667 if ( is_array( $info['validate'] ) ) {
668 $validate = $info['validate'];
669 } else {
670 $validator = new XMPValidate( $this->logger );
671 $validate = [ $validator, $info['validate'] ];
672 }
673
674 if ( !isset( $this->results['xmp-' . $info['map_group']][$finalName] ) ) {
675 // This can happen if all the members of the struct failed validation.
676 $this->logger->debug( __METHOD__ . " <$ns:$tag> has no valid members." );
677 } elseif ( is_callable( $validate ) ) {
678 $val =& $this->results['xmp-' . $info['map_group']][$finalName];
679 call_user_func_array( $validate, [ $info, &$val, false ] );
680 if ( is_null( $val ) ) {
681 // the idea being the validation function will unset the variable if
682 // its invalid.
683 $this->logger->info( __METHOD__ . " <$ns:$tag> failed validation." );
684 unset( $this->results['xmp-' . $info['map_group']][$finalName] );
685 }
686 } else {
687 $this->logger->warning( __METHOD__ . " Validation function for $finalName ("
688 . $validate[0] . '::' . $validate[1] . '()) is not callable.' );
689 }
690 }
691
692 array_shift( $this->curItem );
693 array_shift( $this->mode );
694 $this->ancestorStruct = false;
695 $this->processingArray = false;
696 $this->itemLang = false;
697 }
698
718 private function endElementModeLi( $elm ) {
719 list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
720 $info = $this->items[$ns][$tag];
721 $finalName = isset( $info['map_name'] )
722 ? $info['map_name'] : $tag;
723
724 array_shift( $this->mode );
725
726 if ( !isset( $this->results['xmp-' . $info['map_group']][$finalName] ) ) {
727 $this->logger->debug( __METHOD__ . " Empty compund element $finalName." );
728
729 return;
730 }
731
732 if ( $elm === self::NS_RDF . ' Seq' ) {
733 $this->results['xmp-' . $info['map_group']][$finalName]['_type'] = 'ol';
734 } elseif ( $elm === self::NS_RDF . ' Bag' ) {
735 $this->results['xmp-' . $info['map_group']][$finalName]['_type'] = 'ul';
736 } elseif ( $elm === self::NS_RDF . ' Alt' ) {
737 // extra if needed as you could theoretically have a non-language alt.
738 if ( $info['mode'] === self::MODE_LANG ) {
739 $this->results['xmp-' . $info['map_group']][$finalName]['_type'] = 'lang';
740 }
741 } else {
742 throw new RuntimeException(
743 __METHOD__ . " expected </rdf:seq> or </rdf:bag> but instead got $elm."
744 );
745 }
746 }
747
758 private function endElementModeQDesc( $elm ) {
759
760 if ( $elm === self::NS_RDF . ' value' ) {
761 list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
762 $this->saveValue( $ns, $tag, $this->charContent );
763
764 return;
765 } else {
766 array_shift( $this->mode );
767 array_shift( $this->curItem );
768 }
769 }
770
784 function endElement( $parser, $elm ) {
785 if ( $elm === ( self::NS_RDF . ' RDF' )
786 || $elm === 'adobe:ns:meta/ xmpmeta'
787 || $elm === 'adobe:ns:meta/ xapmeta'
788 ) {
789 // ignore these.
790 return;
791 }
792
793 if ( $elm === self::NS_RDF . ' type' ) {
794 // these aren't really supported properly yet.
795 // However, it appears they almost never used.
796 $this->logger->info( __METHOD__ . ' encountered <rdf:type>' );
797 }
798
799 if ( strpos( $elm, ' ' ) === false ) {
800 // This probably shouldn't happen.
801 // However, there is a bug in an adobe product
802 // that forgets the namespace on some things.
803 // (Luckily they are unimportant things).
804 $this->logger->info( __METHOD__ . " Encountered </$elm> which has no namespace. Skipping." );
805
806 return;
807 }
808
809 if ( count( $this->mode[0] ) === 0 ) {
810 // This should never ever happen and means
811 // there is a pretty major bug in this class.
812 throw new RuntimeException( 'Encountered end element with no mode' );
813 }
814
815 if ( count( $this->curItem ) == 0 && $this->mode[0] !== self::MODE_INITIAL ) {
816 // just to be paranoid. Should always have a curItem, except for initially
817 // (aka during MODE_INITAL).
818 throw new RuntimeException( "Hit end element </$elm> but no curItem" );
819 }
820
821 switch ( $this->mode[0] ) {
823 $this->endElementModeIgnore( $elm );
824 break;
826 $this->endElementModeSimple( $elm );
827 break;
829 case self::MODE_SEQ:
830 case self::MODE_BAG:
831 case self::MODE_LANG:
833 $this->endElementNested( $elm );
834 break;
836 if ( $elm === self::NS_RDF . ' Description' ) {
837 array_shift( $this->mode );
838 } else {
839 throw new RuntimeException( 'Element ended unexpectedly while in MODE_INITIAL' );
840 }
841 break;
842 case self::MODE_LI:
844 $this->endElementModeLi( $elm );
845 break;
846 case self::MODE_QDESC:
847 $this->endElementModeQDesc( $elm );
848 break;
849 default:
850 $this->logger->warning( __METHOD__ . " no mode (elm = $elm)" );
851 break;
852 }
853 }
854
866 private function startElementModeIgnore( $elm ) {
867 if ( $elm === $this->curItem[0] ) {
868 array_unshift( $this->curItem, $elm );
869 array_unshift( $this->mode, self::MODE_IGNORE );
870 }
871 }
872
880 private function startElementModeBag( $elm ) {
881 if ( $elm === self::NS_RDF . ' Bag' ) {
882 array_unshift( $this->mode, self::MODE_LI );
883 } else {
884 throw new RuntimeException( "Expected <rdf:Bag> but got $elm." );
885 }
886 }
887
895 private function startElementModeSeq( $elm ) {
896 if ( $elm === self::NS_RDF . ' Seq' ) {
897 array_unshift( $this->mode, self::MODE_LI );
898 } elseif ( $elm === self::NS_RDF . ' Bag' ) {
899 # bug 27105
900 $this->logger->info( __METHOD__ . ' Expected an rdf:Seq, but got an rdf:Bag. Pretending'
901 . ' it is a Seq, since some buggy software is known to screw this up.' );
902 array_unshift( $this->mode, self::MODE_LI );
903 } else {
904 throw new RuntimeException( "Expected <rdf:Seq> but got $elm." );
905 }
906 }
907
922 private function startElementModeLang( $elm ) {
923 if ( $elm === self::NS_RDF . ' Alt' ) {
924 array_unshift( $this->mode, self::MODE_LI_LANG );
925 } else {
926 throw new RuntimeException( "Expected <rdf:Seq> but got $elm." );
927 }
928 }
929
948 private function startElementModeSimple( $elm, $attribs ) {
949 if ( $elm === self::NS_RDF . ' Description' ) {
950 // If this value has qualifiers
951 array_unshift( $this->mode, self::MODE_QDESC );
952 array_unshift( $this->curItem, $this->curItem[0] );
953
954 if ( isset( $attribs[self::NS_RDF . ' value'] ) ) {
955 list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
956 $this->saveValue( $ns, $tag, $attribs[self::NS_RDF . ' value'] );
957 }
958 } elseif ( $elm === self::NS_RDF . ' value' ) {
959 // This should not be here.
960 throw new RuntimeException( __METHOD__ . ' Encountered <rdf:value> where it was unexpected.' );
961 } else {
962 // something else we don't recognize, like a qualifier maybe.
963 $this->logger->info( __METHOD__ .
964 " Encountered element <$elm> where only expecting character data as value of " .
965 $this->curItem[0] );
966 array_unshift( $this->mode, self::MODE_IGNORE );
967 array_unshift( $this->curItem, $elm );
968 }
969 }
970
985 private function startElementModeQDesc( $elm ) {
986 if ( $elm === self::NS_RDF . ' value' ) {
987 return; // do nothing
988 } else {
989 // otherwise its a qualifier, which we ignore
990 array_unshift( $this->mode, self::MODE_IGNORE );
991 array_unshift( $this->curItem, $elm );
992 }
993 }
994
1007 private function startElementModeInitial( $ns, $tag, $attribs ) {
1008 if ( $ns !== self::NS_RDF ) {
1009
1010 if ( isset( $this->items[$ns][$tag] ) ) {
1011 if ( isset( $this->items[$ns][$tag]['structPart'] ) ) {
1012 // If this element is supposed to appear only as
1013 // a child of a structure, but appears here (not as
1014 // a child of a struct), then something weird is
1015 // happening, so ignore this element and its children.
1016
1017 $this->logger->warning( "Encountered <$ns:$tag> outside"
1018 . " of its expected parent. Ignoring." );
1019
1020 array_unshift( $this->mode, self::MODE_IGNORE );
1021 array_unshift( $this->curItem, $ns . ' ' . $tag );
1022
1023 return;
1024 }
1025 $mode = $this->items[$ns][$tag]['mode'];
1026 array_unshift( $this->mode, $mode );
1027 array_unshift( $this->curItem, $ns . ' ' . $tag );
1028 if ( $mode === self::MODE_STRUCT ) {
1029 $this->ancestorStruct = isset( $this->items[$ns][$tag]['map_name'] )
1030 ? $this->items[$ns][$tag]['map_name'] : $tag;
1031 }
1032 if ( $this->charContent !== false ) {
1033 // Something weird.
1034 // Should not happen in valid XMP.
1035 throw new RuntimeException( 'tag nested in non-whitespace characters.' );
1036 }
1037 } else {
1038 // This element is not on our list of allowed elements so ignore.
1039 $this->logger->debug( __METHOD__ . " Ignoring unrecognized element <$ns:$tag>." );
1040 array_unshift( $this->mode, self::MODE_IGNORE );
1041 array_unshift( $this->curItem, $ns . ' ' . $tag );
1042
1043 return;
1044 }
1045 }
1046 // process attributes
1047 $this->doAttribs( $attribs );
1048 }
1049
1069 private function startElementModeStruct( $ns, $tag, $attribs ) {
1070 if ( $ns !== self::NS_RDF ) {
1071
1072 if ( isset( $this->items[$ns][$tag] ) ) {
1073 if ( isset( $this->items[$ns][$this->ancestorStruct]['children'] )
1074 && !isset( $this->items[$ns][$this->ancestorStruct]['children'][$tag] )
1075 ) {
1076 // This assumes that we don't have inter-namespace nesting
1077 // which we don't in all the properties we're interested in.
1078 throw new RuntimeException( " <$tag> appeared nested in <" . $this->ancestorStruct
1079 . "> where it is not allowed." );
1080 }
1081 array_unshift( $this->mode, $this->items[$ns][$tag]['mode'] );
1082 array_unshift( $this->curItem, $ns . ' ' . $tag );
1083 if ( $this->charContent !== false ) {
1084 // Something weird.
1085 // Should not happen in valid XMP.
1086 throw new RuntimeException( "tag <$tag> nested in non-whitespace characters (" .
1087 $this->charContent . ")." );
1088 }
1089 } else {
1090 array_unshift( $this->mode, self::MODE_IGNORE );
1091 array_unshift( $this->curItem, $elm );
1092
1093 return;
1094 }
1095 }
1096
1097 if ( $ns === self::NS_RDF && $tag === 'Description' ) {
1098 $this->doAttribs( $attribs );
1099 array_unshift( $this->mode, self::MODE_STRUCT );
1100 array_unshift( $this->curItem, $this->curItem[0] );
1101 }
1102 }
1103
1117 private function startElementModeLi( $elm, $attribs ) {
1118 if ( ( $elm ) !== self::NS_RDF . ' li' ) {
1119 throw new RuntimeException( "<rdf:li> expected but got $elm." );
1120 }
1121
1122 if ( !isset( $this->mode[1] ) ) {
1123 // This should never ever ever happen. Checking for it
1124 // to be paranoid.
1125 throw new RuntimeException( 'In mode Li, but no 2xPrevious mode!' );
1126 }
1127
1128 if ( $this->mode[1] === self::MODE_BAGSTRUCT ) {
1129 // This list item contains a compound (STRUCT) value.
1130 array_unshift( $this->mode, self::MODE_STRUCT );
1131 array_unshift( $this->curItem, $elm );
1132 $this->processingArray = true;
1133
1134 if ( !isset( $this->curItem[1] ) ) {
1135 // be paranoid.
1136 throw new RuntimeException( 'Can not find parent of BAGSTRUCT.' );
1137 }
1138 list( $curNS, $curTag ) = explode( ' ', $this->curItem[1] );
1139 $this->ancestorStruct = isset( $this->items[$curNS][$curTag]['map_name'] )
1140 ? $this->items[$curNS][$curTag]['map_name'] : $curTag;
1141
1142 $this->doAttribs( $attribs );
1143 } else {
1144 // Normal BAG or SEQ containing simple values.
1145 array_unshift( $this->mode, self::MODE_SIMPLE );
1146 // need to add curItem[0] on again since one is for the specific item
1147 // and one is for the entire group.
1148 array_unshift( $this->curItem, $this->curItem[0] );
1149 $this->processingArray = true;
1150 }
1151 }
1152
1167 private function startElementModeLiLang( $elm, $attribs ) {
1168 if ( $elm !== self::NS_RDF . ' li' ) {
1169 throw new RuntimeException( __METHOD__ . " <rdf:li> expected but got $elm." );
1170 }
1171 if ( !isset( $attribs[self::NS_XML . ' lang'] )
1172 || !preg_match( '/^[-A-Za-z0-9]{2,}$/D', $attribs[self::NS_XML . ' lang'] )
1173 ) {
1174 throw new RuntimeException( __METHOD__
1175 . " <rdf:li> did not contain, or has invalid xml:lang attribute in lang alternative" );
1176 }
1177
1178 // Lang is case-insensitive.
1179 $this->itemLang = strtolower( $attribs[self::NS_XML . ' lang'] );
1180
1181 // need to add curItem[0] on again since one is for the specific item
1182 // and one is for the entire group.
1183 array_unshift( $this->curItem, $this->curItem[0] );
1184 array_unshift( $this->mode, self::MODE_SIMPLE );
1185 $this->processingArray = true;
1186 }
1187
1198 function startElement( $parser, $elm, $attribs ) {
1199
1200 if ( $elm === self::NS_RDF . ' RDF'
1201 || $elm === 'adobe:ns:meta/ xmpmeta'
1202 || $elm === 'adobe:ns:meta/ xapmeta'
1203 ) {
1204 /* ignore. */
1205 return;
1206 } elseif ( $elm === self::NS_RDF . ' Description' ) {
1207 if ( count( $this->mode ) === 0 ) {
1208 // outer rdf:desc
1209 array_unshift( $this->mode, self::MODE_INITIAL );
1210 }
1211 } elseif ( $elm === self::NS_RDF . ' type' ) {
1212 // This doesn't support rdf:type properly.
1213 // In practise I have yet to see a file that
1214 // uses this element, however it is mentioned
1215 // on page 25 of part 1 of the xmp standard.
1216 // Also it seems as if exiv2 and exiftool do not support
1217 // this either (That or I misunderstand the standard)
1218 $this->logger->info( __METHOD__ . ' Encountered <rdf:type> which isn\'t currently supported' );
1219 }
1220
1221 if ( strpos( $elm, ' ' ) === false ) {
1222 // This probably shouldn't happen.
1223 $this->logger->info( __METHOD__ . " Encountered <$elm> which has no namespace. Skipping." );
1224
1225 return;
1226 }
1227
1228 list( $ns, $tag ) = explode( ' ', $elm, 2 );
1229
1230 if ( count( $this->mode ) === 0 ) {
1231 // This should not happen.
1232 throw new RuntimeException( 'Error extracting XMP, '
1233 . "encountered <$elm> with no mode" );
1234 }
1235
1236 switch ( $this->mode[0] ) {
1237 case self::MODE_IGNORE:
1238 $this->startElementModeIgnore( $elm );
1239 break;
1240 case self::MODE_SIMPLE:
1241 $this->startElementModeSimple( $elm, $attribs );
1242 break;
1243 case self::MODE_INITIAL:
1244 $this->startElementModeInitial( $ns, $tag, $attribs );
1245 break;
1246 case self::MODE_STRUCT:
1247 $this->startElementModeStruct( $ns, $tag, $attribs );
1248 break;
1249 case self::MODE_BAG:
1251 $this->startElementModeBag( $elm );
1252 break;
1253 case self::MODE_SEQ:
1254 $this->startElementModeSeq( $elm );
1255 break;
1256 case self::MODE_LANG:
1257 $this->startElementModeLang( $elm );
1258 break;
1259 case self::MODE_LI_LANG:
1260 $this->startElementModeLiLang( $elm, $attribs );
1261 break;
1262 case self::MODE_LI:
1263 $this->startElementModeLi( $elm, $attribs );
1264 break;
1265 case self::MODE_QDESC:
1266 $this->startElementModeQDesc( $elm );
1267 break;
1268 default:
1269 throw new RuntimeException( 'StartElement in unknown mode: ' . $this->mode[0] );
1270 }
1271 }
1272
1273 // @codingStandardsIgnoreStart Generic.Files.LineLength
1289 // @codingStandardsIgnoreEnd
1290 private function doAttribs( $attribs ) {
1291 // first check for rdf:parseType attribute, as that can change
1292 // how the attributes are interperted.
1293
1294 if ( isset( $attribs[self::NS_RDF . ' parseType'] )
1295 && $attribs[self::NS_RDF . ' parseType'] === 'Resource'
1296 && $this->mode[0] === self::MODE_SIMPLE
1297 ) {
1298 // this is equivalent to having an inner rdf:Description
1299 $this->mode[0] = self::MODE_QDESC;
1300 }
1301 foreach ( $attribs as $name => $val ) {
1302 if ( strpos( $name, ' ' ) === false ) {
1303 // This shouldn't happen, but so far some old software forgets namespace
1304 // on rdf:about.
1305 $this->logger->info( __METHOD__ . ' Encountered non-namespaced attribute: '
1306 . " $name=\"$val\". Skipping. " );
1307 continue;
1308 }
1309 list( $ns, $tag ) = explode( ' ', $name, 2 );
1310 if ( $ns === self::NS_RDF ) {
1311 if ( $tag === 'value' || $tag === 'resource' ) {
1312 // resource is for url.
1313 // value attribute is a weird way of just putting the contents.
1314 $this->char( $this->xmlParser, $val );
1315 }
1316 } elseif ( isset( $this->items[$ns][$tag] ) ) {
1317 if ( $this->mode[0] === self::MODE_SIMPLE ) {
1318 throw new RuntimeException( __METHOD__
1319 . " $ns:$tag found as attribute where not allowed" );
1320 }
1321 $this->saveValue( $ns, $tag, $val );
1322 } else {
1323 $this->logger->debug( __METHOD__ . " Ignoring unrecognized element <$ns:$tag>." );
1324 }
1325 }
1326 }
1327
1339 private function saveValue( $ns, $tag, $val ) {
1340
1341 $info =& $this->items[$ns][$tag];
1342 $finalName = isset( $info['map_name'] )
1343 ? $info['map_name'] : $tag;
1344 if ( isset( $info['validate'] ) ) {
1345 if ( is_array( $info['validate'] ) ) {
1346 $validate = $info['validate'];
1347 } else {
1348 $validator = new XMPValidate( $this->logger );
1349 $validate = [ $validator, $info['validate'] ];
1350 }
1351
1352 if ( is_callable( $validate ) ) {
1353 call_user_func_array( $validate, [ $info, &$val, true ] );
1354 // the reasoning behind using &$val instead of using the return value
1355 // is to be consistent between here and validating structures.
1356 if ( is_null( $val ) ) {
1357 $this->logger->info( __METHOD__ . " <$ns:$tag> failed validation." );
1358
1359 return;
1360 }
1361 } else {
1362 $this->logger->warning( __METHOD__ . " Validation function for $finalName ("
1363 . $validate[0] . '::' . $validate[1] . '()) is not callable.' );
1364 }
1365 }
1366
1367 if ( $this->ancestorStruct && $this->processingArray ) {
1368 // Aka both an array and a struct. ( self::MODE_BAGSTRUCT )
1369 $this->results['xmp-' . $info['map_group']][$this->ancestorStruct][][$finalName] = $val;
1370 } elseif ( $this->ancestorStruct ) {
1371 $this->results['xmp-' . $info['map_group']][$this->ancestorStruct][$finalName] = $val;
1372 } elseif ( $this->processingArray ) {
1373 if ( $this->itemLang === false ) {
1374 // normal array
1375 $this->results['xmp-' . $info['map_group']][$finalName][] = $val;
1376 } else {
1377 // lang array.
1378 $this->results['xmp-' . $info['map_group']][$finalName][$this->itemLang] = $val;
1379 }
1380 } else {
1381 $this->results['xmp-' . $info['map_group']][$finalName] = $val;
1382 }
1383 }
1384}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
$line
Definition cdb.php:59
static getItems()
Get the items array.
Definition XMPInfo.php:33
Class for reading xmp data containing properties relevant to images, and spitting out an array that F...
Definition XMP.php:54
const NS_XML
Definition XMP.php:120
const NS_RDF
Definition XMP.php:119
bool string $charContent
Temporary holder for character data that appears in xmp doc.
Definition XMP.php:65
const MODE_SEQ
Definition XMP.php:113
const MODE_LANG
Definition XMP.php:115
bool string $itemLang
Used for lang alts only.
Definition XMP.php:77
destroyXMLParser()
free the XML parser.
Definition XMP.php:166
const MODE_STRUCT
Definition XMP.php:112
const PARSABLE_BUFFERING
Definition XMP.php:125
LoggerInterface $logger
Definition XMP.php:131
const PARSABLE_NO
Definition XMP.php:126
const PARSABLE_OK
Definition XMP.php:124
endElementModeIgnore( $elm)
When we hit a closing element in MODE_IGNORE Check to see if this is the element we started to ignore...
Definition XMP.php:591
const MODE_LI
Definition XMP.php:105
char( $parser, $data)
Character data handler Called whenever character data is found in the xmp document.
Definition XMP.php:499
setLogger(LoggerInterface $logger)
Definition XMP.php:155
getResults()
Get the result array.
Definition XMP.php:208
startElementModeLang( $elm)
Start element in MODE_LANG (language alternative) this should always be <rdf:Alt>
Definition XMP.php:922
array $results
Array to hold results.
Definition XMP.php:71
bool string $charset
Character set like 'UTF-8'.
Definition XMP.php:83
doAttribs( $attribs)
Process attributes.
Definition XMP.php:1290
parseExtended( $content)
Entry point for XMPExtended blocks in jpeg files.
Definition XMP.php:419
endElementModeSimple( $elm)
Hit a closing element when in MODE_SIMPLE.
Definition XMP.php:613
array $curItem
Array to hold the current element (and previous element, and so on)
Definition XMP.php:59
const PARSABLE_UNKNOWN
Definition XMP.php:123
startElementModeStruct( $ns, $tag, $attribs)
Hit an opening element when in a Struct (MODE_STRUCT) This is generally for fields of a compound prop...
Definition XMP.php:1069
static isSupported()
Check if this instance supports using this class.
Definition XMP.php:198
parse( $content, $allOfIt=true)
Main function to call to parse XMP.
Definition XMP.php:303
array $mode
Stores the state the xmpreader is in (see MODE_FOO constants)
Definition XMP.php:68
int $parsable
Flag determining if the XMP is safe to parse.
Definition XMP.php:89
resetXMLParser()
Main use is if a single item has multiple xmp documents describing it.
Definition XMP.php:177
startElement( $parser, $elm, $attribs)
Hits an opening element.
Definition XMP.php:1198
startElementModeBag( $elm)
Start element in MODE_BAG (unordered array) this should always be <rdf:Bag>
Definition XMP.php:880
const MODE_INITIAL
These are various mode constants.
Definition XMP.php:103
const MODE_ALT
Definition XMP.php:116
startElementModeQDesc( $elm)
Start an element when in MODE_QDESC.
Definition XMP.php:985
__construct(LoggerInterface $logger=null)
Constructor.
Definition XMP.php:138
startElementModeInitial( $ns, $tag, $attribs)
Starting an element when in MODE_INITIAL This usually happens when we hit an element inside the outer...
Definition XMP.php:1007
const MODE_LI_LANG
Definition XMP.php:106
saveValue( $ns, $tag, $val)
Given an extracted value, save it to results array.
Definition XMP.php:1339
endElement( $parser, $elm)
Handler for hitting a closing element.
Definition XMP.php:784
startElementModeSeq( $elm)
Start element in MODE_SEQ (ordered array) this should always be <rdf:Seq>
Definition XMP.php:895
string $xmlParsableBuffer
Buffer of XML to parse.
Definition XMP.php:92
const MODE_QDESC
Definition XMP.php:107
const MODE_SIMPLE
Definition XMP.php:111
const MODE_BAG
Definition XMP.php:114
endElementModeLi( $elm)
Hit a closing element in MODE_LI (either rdf:Seq, or rdf:Bag ) Add information about what type of ele...
Definition XMP.php:718
array $items
XMP item configuration array.
Definition XMP.php:56
startElementModeSimple( $elm, $attribs)
Handle an opening element when in MODE_SIMPLE.
Definition XMP.php:948
const MODE_BAGSTRUCT
Definition XMP.php:117
resource $xmlParser
A resource handle for the XML parser.
Definition XMP.php:80
endElementNested( $elm)
Hit a closing element in MODE_STRUCT, MODE_SEQ, MODE_BAG generally means we've finished processing a ...
Definition XMP.php:648
startElementModeIgnore( $elm)
Hit an opening element while in MODE_IGNORE.
Definition XMP.php:866
startElementModeLi( $elm, $attribs)
opening element in MODE_LI process elements of arrays.
Definition XMP.php:1117
bool $processingArray
If we're doing a seq or bag.
Definition XMP.php:74
const MODE_IGNORE
Definition XMP.php:104
bool string $ancestorStruct
The structure name when processing nested structures.
Definition XMP.php:62
int $extendedXMPOffset
Definition XMP.php:86
endElementModeQDesc( $elm)
End element while in MODE_QDESC mostly when ending an element when we have a simple value that has qu...
Definition XMP.php:758
checkParseSafety( $content)
Check if a block of XML is safe to pass to xml_parse, i.e.
Definition XMP.php:536
startElementModeLiLang( $elm, $attribs)
Opening element in MODE_LI_LANG.
Definition XMP.php:1167
This contains some static methods for validating XMP properties.
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 document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
the array() calling protocol came about after MediaWiki 1.4rc1.
namespace are movable Hooks may change this value to override the return value of MWNamespace::isMovable(). 'NewDifferenceEngine' do that in ParserLimitReportFormat instead $parser
Definition hooks.txt:2259
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Associative array mapping language codes to prefixed links of the form "language:title". & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition hooks.txt:1937
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content $content
Definition hooks.txt:1094
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books $tag
Definition hooks.txt:1033
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing & $attribs
Definition hooks.txt:1958
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:304
returning false will NOT prevent logging $e
Definition hooks.txt:2110
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 modifiable & $code
Definition hooks.txt:887
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37