MediaWiki REL1_29
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
53class XMPReader implements LoggerAwareInterface {
55 protected $items;
56
58 private $curItem = [];
59
61 private $ancestorStruct = false;
62
64 private $charContent = false;
65
67 private $mode = [];
68
70 private $results = [];
71
73 private $processingArray = false;
74
76 private $itemLang = false;
77
79 private $xmlParser;
80
82 private $charset = false;
83
85 private $extendedXMPOffset = 0;
86
88 private $parsable = 0;
89
91 private $xmlParsableBuffer = '';
92
102 const MODE_INITIAL = 0;
103 const MODE_IGNORE = 1;
104 const MODE_LI = 2;
105 const MODE_LI_LANG = 3;
106 const MODE_QDESC = 4;
107
108 // The following MODE constants are also used in the
109 // $items array to denote what type of property the item is.
110 const MODE_SIMPLE = 10;
111 const MODE_STRUCT = 11; // structure (associative array)
112 const MODE_SEQ = 12; // ordered list
113 const MODE_BAG = 13; // unordered list
114 const MODE_LANG = 14;
115 const MODE_ALT = 15; // non-language alt. Currently not implemented, and not needed atm.
116 const MODE_BAGSTRUCT = 16; // A BAG of Structs.
117
118 const NS_RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
119 const NS_XML = 'http://www.w3.org/XML/1998/namespace';
120
121 // States used while determining if XML is safe to parse
123 const PARSABLE_OK = 1;
125 const PARSABLE_NO = 3;
126
130 private $logger;
131
137 function __construct( LoggerInterface $logger = null ) {
138
139 if ( !function_exists( 'xml_parser_create_ns' ) ) {
140 // this should already be checked by this point
141 throw new RuntimeException( 'XMP support requires XML Parser' );
142 }
143 if ( $logger ) {
144 $this->setLogger( $logger );
145 } else {
146 $this->setLogger( new NullLogger() );
147 }
148
149 $this->items = XMPInfo::getItems();
150
151 $this->resetXMLParser();
152 }
153
154 public function setLogger( LoggerInterface $logger ) {
155 $this->logger = $logger;
156 }
157
165 private function destroyXMLParser() {
166 if ( $this->xmlParser ) {
167 xml_parser_free( $this->xmlParser );
168 $this->xmlParser = null;
169 }
170 }
171
176 private function resetXMLParser() {
177
178 $this->destroyXMLParser();
179
180 $this->xmlParser = xml_parser_create_ns( 'UTF-8', ' ' );
181 xml_parser_set_option( $this->xmlParser, XML_OPTION_CASE_FOLDING, 0 );
182 xml_parser_set_option( $this->xmlParser, XML_OPTION_SKIP_WHITE, 1 );
183
184 xml_set_element_handler( $this->xmlParser,
185 [ $this, 'startElement' ],
186 [ $this, 'endElement' ] );
187
188 xml_set_character_data_handler( $this->xmlParser, [ $this, 'char' ] );
189
190 $this->parsable = self::PARSABLE_UNKNOWN;
191 $this->xmlParsableBuffer = '';
192 }
193
197 public static function isSupported() {
198 return function_exists( 'xml_parser_create_ns' ) && class_exists( 'XMLReader' );
199 }
200
207 public function getResults() {
208 // xmp-special is for metadata that affects how stuff
209 // is extracted. For example xmpNote:HasExtendedXMP.
210
211 // It is also used to handle photoshop:AuthorsPosition
212 // which is weird and really part of another property,
213 // see 2:85 in IPTC. See also pg 21 of IPTC4XMP standard.
214 // The location fields also use it.
215
216 $data = $this->results;
217
218 if ( isset( $data['xmp-special']['AuthorsPosition'] )
219 && is_string( $data['xmp-special']['AuthorsPosition'] )
220 && isset( $data['xmp-general']['Artist'][0] )
221 ) {
222 // Note, if there is more than one creator,
223 // this only applies to first. This also will
224 // only apply to the dc:Creator prop, not the
225 // exif:Artist prop.
226
227 $data['xmp-general']['Artist'][0] =
228 $data['xmp-special']['AuthorsPosition'] . ', '
229 . $data['xmp-general']['Artist'][0];
230 }
231
232 // Go through the LocationShown and LocationCreated
233 // changing it to the non-hierarchal form used by
234 // the other location fields.
235
236 if ( isset( $data['xmp-special']['LocationShown'][0] )
237 && is_array( $data['xmp-special']['LocationShown'][0] )
238 ) {
239 // the is_array is just paranoia. It should always
240 // be an array.
241 foreach ( $data['xmp-special']['LocationShown'] as $loc ) {
242 if ( !is_array( $loc ) ) {
243 // To avoid copying over the _type meta-fields.
244 continue;
245 }
246 foreach ( $loc as $field => $val ) {
247 $data['xmp-general'][$field . 'Dest'][] = $val;
248 }
249 }
250 }
251 if ( isset( $data['xmp-special']['LocationCreated'][0] )
252 && is_array( $data['xmp-special']['LocationCreated'][0] )
253 ) {
254 // the is_array is just paranoia. It should always
255 // be an array.
256 foreach ( $data['xmp-special']['LocationCreated'] as $loc ) {
257 if ( !is_array( $loc ) ) {
258 // To avoid copying over the _type meta-fields.
259 continue;
260 }
261 foreach ( $loc as $field => $val ) {
262 $data['xmp-general'][$field . 'Created'][] = $val;
263 }
264 }
265 }
266
267 // We don't want to return the special values, since they're
268 // special and not info to be stored about the file.
269 unset( $data['xmp-special'] );
270
271 // Convert GPSAltitude to negative if below sea level.
272 if ( isset( $data['xmp-exif']['GPSAltitudeRef'] )
273 && isset( $data['xmp-exif']['GPSAltitude'] )
274 ) {
275
276 // Must convert to a real before multiplying by -1
277 // XMPValidate guarantees there will always be a '/' in this value.
278 list( $nom, $denom ) = explode( '/', $data['xmp-exif']['GPSAltitude'] );
279 $data['xmp-exif']['GPSAltitude'] = $nom / $denom;
280
281 if ( $data['xmp-exif']['GPSAltitudeRef'] == '1' ) {
282 $data['xmp-exif']['GPSAltitude'] *= -1;
283 }
284 unset( $data['xmp-exif']['GPSAltitudeRef'] );
285 }
286
287 return $data;
288 }
289
302 public function parse( $content, $allOfIt = true ) {
303 if ( !$this->xmlParser ) {
304 $this->resetXMLParser();
305 }
306 try {
307
308 // detect encoding by looking for BOM which is supposed to be in processing instruction.
309 // see page 12 of http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart3.pdf
310 if ( !$this->charset ) {
311 $bom = [];
312 if ( preg_match( '/\xEF\xBB\xBF|\xFE\xFF|\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\xFF\xFE/',
313 $content, $bom )
314 ) {
315 switch ( $bom[0] ) {
316 case "\xFE\xFF":
317 $this->charset = 'UTF-16BE';
318 break;
319 case "\xFF\xFE":
320 $this->charset = 'UTF-16LE';
321 break;
322 case "\x00\x00\xFE\xFF":
323 $this->charset = 'UTF-32BE';
324 break;
325 case "\xFF\xFE\x00\x00":
326 $this->charset = 'UTF-32LE';
327 break;
328 case "\xEF\xBB\xBF":
329 $this->charset = 'UTF-8';
330 break;
331 default:
332 // this should be impossible to get to
333 throw new RuntimeException( "Invalid BOM" );
334 }
335 } else {
336 // standard specifically says, if no bom assume utf-8
337 $this->charset = 'UTF-8';
338 }
339 }
340 if ( $this->charset !== 'UTF-8' ) {
341 // don't convert if already utf-8
342 MediaWiki\suppressWarnings();
343 $content = iconv( $this->charset, 'UTF-8//IGNORE', $content );
344 MediaWiki\restoreWarnings();
345 }
346
347 // Ensure the XMP block does not have an xml doctype declaration, which
348 // could declare entities unsafe to parse with xml_parse (T85848/T71210).
349 if ( $this->parsable !== self::PARSABLE_OK ) {
350 if ( $this->parsable === self::PARSABLE_NO ) {
351 throw new RuntimeException( 'Unsafe doctype declaration in XML.' );
352 }
353
354 $content = $this->xmlParsableBuffer . $content;
355 if ( !$this->checkParseSafety( $content ) ) {
356 if ( !$allOfIt && $this->parsable !== self::PARSABLE_NO ) {
357 // parse wasn't Unsuccessful yet, so return true
358 // in this case.
359 return true;
360 }
361 $msg = ( $this->parsable === self::PARSABLE_NO ) ?
362 'Unsafe doctype declaration in XML.' :
363 'No root element found in XML.';
364 throw new RuntimeException( $msg );
365 }
366 }
367
368 $ok = xml_parse( $this->xmlParser, $content, $allOfIt );
369 if ( !$ok ) {
370 $code = xml_get_error_code( $this->xmlParser );
371 $error = xml_error_string( $code );
372 $line = xml_get_current_line_number( $this->xmlParser );
373 $col = xml_get_current_column_number( $this->xmlParser );
374 $offset = xml_get_current_byte_index( $this->xmlParser );
375
376 $this->logger->warning(
377 '{method} : Error reading XMP content: {error} ' .
378 '(line: {line} column: {column} byte offset: {offset})',
379 [
380 'method' => __METHOD__,
381 'error_code' => $code,
382 'error' => $error,
383 'line' => $line,
384 'column' => $col,
385 'offset' => $offset,
386 'content' => $content,
387 ] );
388 $this->results = []; // blank if error.
389 $this->destroyXMLParser();
390 return false;
391 }
392 } catch ( Exception $e ) {
393 $this->logger->warning(
394 '{method} Exception caught while parsing: ' . $e->getMessage(),
395 [
396 'method' => __METHOD__,
397 'exception' => $e,
398 'content' => $content,
399 ]
400 );
401 $this->results = [];
402 return false;
403 }
404 if ( $allOfIt ) {
405 $this->destroyXMLParser();
406 }
407
408 return true;
409 }
410
418 public function parseExtended( $content ) {
419 // @todo FIXME: This is untested. Hard to find example files
420 // or programs that make such files..
421 $guid = substr( $content, 0, 32 );
422 if ( !isset( $this->results['xmp-special']['HasExtendedXMP'] )
423 || $this->results['xmp-special']['HasExtendedXMP'] !== $guid
424 ) {
425 $this->logger->info( __METHOD__ .
426 " Ignoring XMPExtended block due to wrong guid (guid= '$guid')" );
427
428 return false;
429 }
430 $len = unpack( 'Nlength/Noffset', substr( $content, 32, 8 ) );
431
432 if ( !$len ||
433 $len['length'] < 4 ||
434 $len['offset'] < 0 ||
435 $len['offset'] > $len['length']
436 ) {
437 $this->logger->info(
438 __METHOD__ . 'Error reading extended XMP block, invalid length or offset.'
439 );
440
441 return false;
442 }
443
444 // we're not very robust here. we should accept it in the wrong order.
445 // To quote the XMP standard:
446 // "A JPEG writer should write the ExtendedXMP marker segments in order,
447 // immediately following the StandardXMP. However, the JPEG standard
448 // does not require preservation of marker segment order. A robust JPEG
449 // reader should tolerate the marker segments in any order."
450 // On the other hand, the probability that an image will have more than
451 // 128k of metadata is rather low... so the probability that it will have
452 // > 128k, and be in the wrong order is very low...
453
454 if ( $len['offset'] !== $this->extendedXMPOffset ) {
455 $this->logger->info( __METHOD__ . 'Ignoring XMPExtended block due to wrong order. (Offset was '
456 . $len['offset'] . ' but expected ' . $this->extendedXMPOffset . ')' );
457
458 return false;
459 }
460
461 if ( $len['offset'] === 0 ) {
462 // if we're starting the extended block, we've probably already
463 // done the XMPStandard block, so reset.
464 $this->resetXMLParser();
465 }
466
467 $this->extendedXMPOffset += $len['length'];
468
469 $actualContent = substr( $content, 40 );
470
471 if ( $this->extendedXMPOffset === strlen( $actualContent ) ) {
472 $atEnd = true;
473 } else {
474 $atEnd = false;
475 }
476
477 $this->logger->debug( __METHOD__ . 'Parsing a XMPExtended block' );
478
479 return $this->parse( $actualContent, $atEnd );
480 }
481
498 function char( $parser, $data ) {
499
500 $data = trim( $data );
501 if ( trim( $data ) === "" ) {
502 return;
503 }
504
505 if ( !isset( $this->mode[0] ) ) {
506 throw new RuntimeException( 'Unexpected character data before first rdf:Description element' );
507 }
508
509 if ( $this->mode[0] === self::MODE_IGNORE ) {
510 return;
511 }
512
513 if ( $this->mode[0] !== self::MODE_SIMPLE
514 && $this->mode[0] !== self::MODE_QDESC
515 ) {
516 throw new RuntimeException( 'character data where not expected. (mode ' . $this->mode[0] . ')' );
517 }
518
519 // to check, how does this handle w.s.
520 if ( $this->charContent === false ) {
521 $this->charContent = $data;
522 } else {
523 $this->charContent .= $data;
524 }
525 }
526
535 private function checkParseSafety( $content ) {
536 $reader = new XMLReader();
537 $result = null;
538
539 // For XMLReader to parse incomplete/invalid XML, it has to be open()'ed
540 // instead of using XML().
541 $reader->open(
542 'data://text/plain,' . urlencode( $content ),
543 null,
544 LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_NONET
545 );
546
547 $oldDisable = libxml_disable_entity_loader( true );
549 $reset = new ScopedCallback(
550 'libxml_disable_entity_loader',
551 [ $oldDisable ]
552 );
553 $reader->setParserProperty( XMLReader::SUBST_ENTITIES, false );
554
555 // Even with LIBXML_NOWARNING set, XMLReader::read gives a warning
556 // when parsing truncated XML, which causes unit tests to fail.
557 MediaWiki\suppressWarnings();
558 while ( $reader->read() ) {
559 if ( $reader->nodeType === XMLReader::ELEMENT ) {
560 // Reached the first element without hitting a doctype declaration
561 $this->parsable = self::PARSABLE_OK;
562 $result = true;
563 break;
564 }
565 if ( $reader->nodeType === XMLReader::DOC_TYPE ) {
566 $this->parsable = self::PARSABLE_NO;
567 $result = false;
568 break;
569 }
570 }
571 MediaWiki\restoreWarnings();
572
573 if ( !is_null( $result ) ) {
574 return $result;
575 }
576
577 // Reached the end of the parsable xml without finding an element
578 // or doctype. Buffer and try again.
579 $this->parsable = self::PARSABLE_BUFFERING;
580 $this->xmlParsableBuffer = $content;
581 return false;
582 }
583
590 private function endElementModeIgnore( $elm ) {
591 if ( $this->curItem[0] === $elm ) {
592 array_shift( $this->curItem );
593 array_shift( $this->mode );
594 }
595 }
596
612 private function endElementModeSimple( $elm ) {
613 if ( $this->charContent !== false ) {
614 if ( $this->processingArray ) {
615 // if we're processing an array, use the original element
616 // name instead of rdf:li.
617 list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
618 } else {
619 list( $ns, $tag ) = explode( ' ', $elm, 2 );
620 }
621 $this->saveValue( $ns, $tag, $this->charContent );
622
623 $this->charContent = false; // reset
624 }
625 array_shift( $this->curItem );
626 array_shift( $this->mode );
627 }
628
647 private function endElementNested( $elm ) {
648
649 /* cur item must be the same as $elm, unless if in MODE_STRUCT
650 * in which case it could also be rdf:Description */
651 if ( $this->curItem[0] !== $elm
652 && !( $elm === self::NS_RDF . ' Description'
653 && $this->mode[0] === self::MODE_STRUCT )
654 ) {
655 throw new RuntimeException( "nesting mismatch. got a </$elm> but expected a </" .
656 $this->curItem[0] . '>' );
657 }
658
659 // Validate structures.
660 list( $ns, $tag ) = explode( ' ', $elm, 2 );
661 if ( isset( $this->items[$ns][$tag]['validate'] ) ) {
662 $info =& $this->items[$ns][$tag];
663 $finalName = isset( $info['map_name'] )
664 ? $info['map_name'] : $tag;
665
666 if ( is_array( $info['validate'] ) ) {
667 $validate = $info['validate'];
668 } else {
669 $validator = new XMPValidate( $this->logger );
670 $validate = [ $validator, $info['validate'] ];
671 }
672
673 if ( !isset( $this->results['xmp-' . $info['map_group']][$finalName] ) ) {
674 // This can happen if all the members of the struct failed validation.
675 $this->logger->debug( __METHOD__ . " <$ns:$tag> has no valid members." );
676 } elseif ( is_callable( $validate ) ) {
677 $val =& $this->results['xmp-' . $info['map_group']][$finalName];
678 call_user_func_array( $validate, [ $info, &$val, false ] );
679 if ( is_null( $val ) ) {
680 // the idea being the validation function will unset the variable if
681 // its invalid.
682 $this->logger->info( __METHOD__ . " <$ns:$tag> failed validation." );
683 unset( $this->results['xmp-' . $info['map_group']][$finalName] );
684 }
685 } else {
686 $this->logger->warning( __METHOD__ . " Validation function for $finalName ("
687 . $validate[0] . '::' . $validate[1] . '()) is not callable.' );
688 }
689 }
690
691 array_shift( $this->curItem );
692 array_shift( $this->mode );
693 $this->ancestorStruct = false;
694 $this->processingArray = false;
695 $this->itemLang = false;
696 }
697
717 private function endElementModeLi( $elm ) {
718 list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
719 $info = $this->items[$ns][$tag];
720 $finalName = isset( $info['map_name'] )
721 ? $info['map_name'] : $tag;
722
723 array_shift( $this->mode );
724
725 if ( !isset( $this->results['xmp-' . $info['map_group']][$finalName] ) ) {
726 $this->logger->debug( __METHOD__ . " Empty compund element $finalName." );
727
728 return;
729 }
730
731 if ( $elm === self::NS_RDF . ' Seq' ) {
732 $this->results['xmp-' . $info['map_group']][$finalName]['_type'] = 'ol';
733 } elseif ( $elm === self::NS_RDF . ' Bag' ) {
734 $this->results['xmp-' . $info['map_group']][$finalName]['_type'] = 'ul';
735 } elseif ( $elm === self::NS_RDF . ' Alt' ) {
736 // extra if needed as you could theoretically have a non-language alt.
737 if ( $info['mode'] === self::MODE_LANG ) {
738 $this->results['xmp-' . $info['map_group']][$finalName]['_type'] = 'lang';
739 }
740 } else {
741 throw new RuntimeException(
742 __METHOD__ . " expected </rdf:seq> or </rdf:bag> but instead got $elm."
743 );
744 }
745 }
746
757 private function endElementModeQDesc( $elm ) {
758
759 if ( $elm === self::NS_RDF . ' value' ) {
760 list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
761 $this->saveValue( $ns, $tag, $this->charContent );
762
763 return;
764 } else {
765 array_shift( $this->mode );
766 array_shift( $this->curItem );
767 }
768 }
769
783 function endElement( $parser, $elm ) {
784 if ( $elm === ( self::NS_RDF . ' RDF' )
785 || $elm === 'adobe:ns:meta/ xmpmeta'
786 || $elm === 'adobe:ns:meta/ xapmeta'
787 ) {
788 // ignore these.
789 return;
790 }
791
792 if ( $elm === self::NS_RDF . ' type' ) {
793 // these aren't really supported properly yet.
794 // However, it appears they almost never used.
795 $this->logger->info( __METHOD__ . ' encountered <rdf:type>' );
796 }
797
798 if ( strpos( $elm, ' ' ) === false ) {
799 // This probably shouldn't happen.
800 // However, there is a bug in an adobe product
801 // that forgets the namespace on some things.
802 // (Luckily they are unimportant things).
803 $this->logger->info( __METHOD__ . " Encountered </$elm> which has no namespace. Skipping." );
804
805 return;
806 }
807
808 if ( count( $this->mode[0] ) === 0 ) {
809 // This should never ever happen and means
810 // there is a pretty major bug in this class.
811 throw new RuntimeException( 'Encountered end element with no mode' );
812 }
813
814 if ( count( $this->curItem ) == 0 && $this->mode[0] !== self::MODE_INITIAL ) {
815 // just to be paranoid. Should always have a curItem, except for initially
816 // (aka during MODE_INITAL).
817 throw new RuntimeException( "Hit end element </$elm> but no curItem" );
818 }
819
820 switch ( $this->mode[0] ) {
822 $this->endElementModeIgnore( $elm );
823 break;
825 $this->endElementModeSimple( $elm );
826 break;
828 case self::MODE_SEQ:
829 case self::MODE_BAG:
830 case self::MODE_LANG:
832 $this->endElementNested( $elm );
833 break;
835 if ( $elm === self::NS_RDF . ' Description' ) {
836 array_shift( $this->mode );
837 } else {
838 throw new RuntimeException( 'Element ended unexpectedly while in MODE_INITIAL' );
839 }
840 break;
841 case self::MODE_LI:
843 $this->endElementModeLi( $elm );
844 break;
845 case self::MODE_QDESC:
846 $this->endElementModeQDesc( $elm );
847 break;
848 default:
849 $this->logger->warning( __METHOD__ . " no mode (elm = $elm)" );
850 break;
851 }
852 }
853
865 private function startElementModeIgnore( $elm ) {
866 if ( $elm === $this->curItem[0] ) {
867 array_unshift( $this->curItem, $elm );
868 array_unshift( $this->mode, self::MODE_IGNORE );
869 }
870 }
871
879 private function startElementModeBag( $elm ) {
880 if ( $elm === self::NS_RDF . ' Bag' ) {
881 array_unshift( $this->mode, self::MODE_LI );
882 } else {
883 throw new RuntimeException( "Expected <rdf:Bag> but got $elm." );
884 }
885 }
886
894 private function startElementModeSeq( $elm ) {
895 if ( $elm === self::NS_RDF . ' Seq' ) {
896 array_unshift( $this->mode, self::MODE_LI );
897 } elseif ( $elm === self::NS_RDF . ' Bag' ) {
898 # T29105
899 $this->logger->info( __METHOD__ . ' Expected an rdf:Seq, but got an rdf:Bag. Pretending'
900 . ' it is a Seq, since some buggy software is known to screw this up.' );
901 array_unshift( $this->mode, self::MODE_LI );
902 } else {
903 throw new RuntimeException( "Expected <rdf:Seq> but got $elm." );
904 }
905 }
906
921 private function startElementModeLang( $elm ) {
922 if ( $elm === self::NS_RDF . ' Alt' ) {
923 array_unshift( $this->mode, self::MODE_LI_LANG );
924 } else {
925 throw new RuntimeException( "Expected <rdf:Seq> but got $elm." );
926 }
927 }
928
947 private function startElementModeSimple( $elm, $attribs ) {
948 if ( $elm === self::NS_RDF . ' Description' ) {
949 // If this value has qualifiers
950 array_unshift( $this->mode, self::MODE_QDESC );
951 array_unshift( $this->curItem, $this->curItem[0] );
952
953 if ( isset( $attribs[self::NS_RDF . ' value'] ) ) {
954 list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
955 $this->saveValue( $ns, $tag, $attribs[self::NS_RDF . ' value'] );
956 }
957 } elseif ( $elm === self::NS_RDF . ' value' ) {
958 // This should not be here.
959 throw new RuntimeException( __METHOD__ . ' Encountered <rdf:value> where it was unexpected.' );
960 } else {
961 // something else we don't recognize, like a qualifier maybe.
962 $this->logger->info( __METHOD__ .
963 " Encountered element <$elm> where only expecting character data as value of " .
964 $this->curItem[0] );
965 array_unshift( $this->mode, self::MODE_IGNORE );
966 array_unshift( $this->curItem, $elm );
967 }
968 }
969
983 private function startElementModeQDesc( $elm ) {
984 if ( $elm === self::NS_RDF . ' value' ) {
985 return; // do nothing
986 } else {
987 // otherwise its a qualifier, which we ignore
988 array_unshift( $this->mode, self::MODE_IGNORE );
989 array_unshift( $this->curItem, $elm );
990 }
991 }
992
1005 private function startElementModeInitial( $ns, $tag, $attribs ) {
1006 if ( $ns !== self::NS_RDF ) {
1007
1008 if ( isset( $this->items[$ns][$tag] ) ) {
1009 if ( isset( $this->items[$ns][$tag]['structPart'] ) ) {
1010 // If this element is supposed to appear only as
1011 // a child of a structure, but appears here (not as
1012 // a child of a struct), then something weird is
1013 // happening, so ignore this element and its children.
1014
1015 $this->logger->warning( "Encountered <$ns:$tag> outside"
1016 . " of its expected parent. Ignoring." );
1017
1018 array_unshift( $this->mode, self::MODE_IGNORE );
1019 array_unshift( $this->curItem, $ns . ' ' . $tag );
1020
1021 return;
1022 }
1023 $mode = $this->items[$ns][$tag]['mode'];
1024 array_unshift( $this->mode, $mode );
1025 array_unshift( $this->curItem, $ns . ' ' . $tag );
1026 if ( $mode === self::MODE_STRUCT ) {
1027 $this->ancestorStruct = isset( $this->items[$ns][$tag]['map_name'] )
1028 ? $this->items[$ns][$tag]['map_name'] : $tag;
1029 }
1030 if ( $this->charContent !== false ) {
1031 // Something weird.
1032 // Should not happen in valid XMP.
1033 throw new RuntimeException( 'tag nested in non-whitespace characters.' );
1034 }
1035 } else {
1036 // This element is not on our list of allowed elements so ignore.
1037 $this->logger->debug( __METHOD__ . " Ignoring unrecognized element <$ns:$tag>." );
1038 array_unshift( $this->mode, self::MODE_IGNORE );
1039 array_unshift( $this->curItem, $ns . ' ' . $tag );
1040
1041 return;
1042 }
1043 }
1044 // process attributes
1045 $this->doAttribs( $attribs );
1046 }
1047
1067 private function startElementModeStruct( $ns, $tag, $attribs ) {
1068 if ( $ns !== self::NS_RDF ) {
1069
1070 if ( isset( $this->items[$ns][$tag] ) ) {
1071 if ( isset( $this->items[$ns][$this->ancestorStruct]['children'] )
1072 && !isset( $this->items[$ns][$this->ancestorStruct]['children'][$tag] )
1073 ) {
1074 // This assumes that we don't have inter-namespace nesting
1075 // which we don't in all the properties we're interested in.
1076 throw new RuntimeException( " <$tag> appeared nested in <" . $this->ancestorStruct
1077 . "> where it is not allowed." );
1078 }
1079 array_unshift( $this->mode, $this->items[$ns][$tag]['mode'] );
1080 array_unshift( $this->curItem, $ns . ' ' . $tag );
1081 if ( $this->charContent !== false ) {
1082 // Something weird.
1083 // Should not happen in valid XMP.
1084 throw new RuntimeException( "tag <$tag> nested in non-whitespace characters (" .
1085 $this->charContent . ")." );
1086 }
1087 } else {
1088 array_unshift( $this->mode, self::MODE_IGNORE );
1089 array_unshift( $this->curItem, $ns . ' ' . $tag );
1090
1091 return;
1092 }
1093 }
1094
1095 if ( $ns === self::NS_RDF && $tag === 'Description' ) {
1096 $this->doAttribs( $attribs );
1097 array_unshift( $this->mode, self::MODE_STRUCT );
1098 array_unshift( $this->curItem, $this->curItem[0] );
1099 }
1100 }
1101
1115 private function startElementModeLi( $elm, $attribs ) {
1116 if ( ( $elm ) !== self::NS_RDF . ' li' ) {
1117 throw new RuntimeException( "<rdf:li> expected but got $elm." );
1118 }
1119
1120 if ( !isset( $this->mode[1] ) ) {
1121 // This should never ever ever happen. Checking for it
1122 // to be paranoid.
1123 throw new RuntimeException( 'In mode Li, but no 2xPrevious mode!' );
1124 }
1125
1126 if ( $this->mode[1] === self::MODE_BAGSTRUCT ) {
1127 // This list item contains a compound (STRUCT) value.
1128 array_unshift( $this->mode, self::MODE_STRUCT );
1129 array_unshift( $this->curItem, $elm );
1130 $this->processingArray = true;
1131
1132 if ( !isset( $this->curItem[1] ) ) {
1133 // be paranoid.
1134 throw new RuntimeException( 'Can not find parent of BAGSTRUCT.' );
1135 }
1136 list( $curNS, $curTag ) = explode( ' ', $this->curItem[1] );
1137 $this->ancestorStruct = isset( $this->items[$curNS][$curTag]['map_name'] )
1138 ? $this->items[$curNS][$curTag]['map_name'] : $curTag;
1139
1140 $this->doAttribs( $attribs );
1141 } else {
1142 // Normal BAG or SEQ containing simple values.
1143 array_unshift( $this->mode, self::MODE_SIMPLE );
1144 // need to add curItem[0] on again since one is for the specific item
1145 // and one is for the entire group.
1146 array_unshift( $this->curItem, $this->curItem[0] );
1147 $this->processingArray = true;
1148 }
1149 }
1150
1165 private function startElementModeLiLang( $elm, $attribs ) {
1166 if ( $elm !== self::NS_RDF . ' li' ) {
1167 throw new RuntimeException( __METHOD__ . " <rdf:li> expected but got $elm." );
1168 }
1169 if ( !isset( $attribs[self::NS_XML . ' lang'] )
1170 || !preg_match( '/^[-A-Za-z0-9]{2,}$/D', $attribs[self::NS_XML . ' lang'] )
1171 ) {
1172 throw new RuntimeException( __METHOD__
1173 . " <rdf:li> did not contain, or has invalid xml:lang attribute in lang alternative" );
1174 }
1175
1176 // Lang is case-insensitive.
1177 $this->itemLang = strtolower( $attribs[self::NS_XML . ' lang'] );
1178
1179 // need to add curItem[0] on again since one is for the specific item
1180 // and one is for the entire group.
1181 array_unshift( $this->curItem, $this->curItem[0] );
1182 array_unshift( $this->mode, self::MODE_SIMPLE );
1183 $this->processingArray = true;
1184 }
1185
1196 function startElement( $parser, $elm, $attribs ) {
1197
1198 if ( $elm === self::NS_RDF . ' RDF'
1199 || $elm === 'adobe:ns:meta/ xmpmeta'
1200 || $elm === 'adobe:ns:meta/ xapmeta'
1201 ) {
1202 /* ignore. */
1203 return;
1204 } elseif ( $elm === self::NS_RDF . ' Description' ) {
1205 if ( count( $this->mode ) === 0 ) {
1206 // outer rdf:desc
1207 array_unshift( $this->mode, self::MODE_INITIAL );
1208 }
1209 } elseif ( $elm === self::NS_RDF . ' type' ) {
1210 // This doesn't support rdf:type properly.
1211 // In practise I have yet to see a file that
1212 // uses this element, however it is mentioned
1213 // on page 25 of part 1 of the xmp standard.
1214 // Also it seems as if exiv2 and exiftool do not support
1215 // this either (That or I misunderstand the standard)
1216 $this->logger->info( __METHOD__ . ' Encountered <rdf:type> which isn\'t currently supported' );
1217 }
1218
1219 if ( strpos( $elm, ' ' ) === false ) {
1220 // This probably shouldn't happen.
1221 $this->logger->info( __METHOD__ . " Encountered <$elm> which has no namespace. Skipping." );
1222
1223 return;
1224 }
1225
1226 list( $ns, $tag ) = explode( ' ', $elm, 2 );
1227
1228 if ( count( $this->mode ) === 0 ) {
1229 // This should not happen.
1230 throw new RuntimeException( 'Error extracting XMP, '
1231 . "encountered <$elm> with no mode" );
1232 }
1233
1234 switch ( $this->mode[0] ) {
1235 case self::MODE_IGNORE:
1236 $this->startElementModeIgnore( $elm );
1237 break;
1238 case self::MODE_SIMPLE:
1239 $this->startElementModeSimple( $elm, $attribs );
1240 break;
1241 case self::MODE_INITIAL:
1242 $this->startElementModeInitial( $ns, $tag, $attribs );
1243 break;
1244 case self::MODE_STRUCT:
1245 $this->startElementModeStruct( $ns, $tag, $attribs );
1246 break;
1247 case self::MODE_BAG:
1249 $this->startElementModeBag( $elm );
1250 break;
1251 case self::MODE_SEQ:
1252 $this->startElementModeSeq( $elm );
1253 break;
1254 case self::MODE_LANG:
1255 $this->startElementModeLang( $elm );
1256 break;
1257 case self::MODE_LI_LANG:
1258 $this->startElementModeLiLang( $elm, $attribs );
1259 break;
1260 case self::MODE_LI:
1261 $this->startElementModeLi( $elm, $attribs );
1262 break;
1263 case self::MODE_QDESC:
1264 $this->startElementModeQDesc( $elm );
1265 break;
1266 default:
1267 throw new RuntimeException( 'StartElement in unknown mode: ' . $this->mode[0] );
1268 }
1269 }
1270
1271 // @codingStandardsIgnoreStart Generic.Files.LineLength
1287 // @codingStandardsIgnoreEnd
1288 private function doAttribs( $attribs ) {
1289 // first check for rdf:parseType attribute, as that can change
1290 // how the attributes are interperted.
1291
1292 if ( isset( $attribs[self::NS_RDF . ' parseType'] )
1293 && $attribs[self::NS_RDF . ' parseType'] === 'Resource'
1294 && $this->mode[0] === self::MODE_SIMPLE
1295 ) {
1296 // this is equivalent to having an inner rdf:Description
1297 $this->mode[0] = self::MODE_QDESC;
1298 }
1299 foreach ( $attribs as $name => $val ) {
1300 if ( strpos( $name, ' ' ) === false ) {
1301 // This shouldn't happen, but so far some old software forgets namespace
1302 // on rdf:about.
1303 $this->logger->info( __METHOD__ . ' Encountered non-namespaced attribute: '
1304 . " $name=\"$val\". Skipping. " );
1305 continue;
1306 }
1307 list( $ns, $tag ) = explode( ' ', $name, 2 );
1308 if ( $ns === self::NS_RDF ) {
1309 if ( $tag === 'value' || $tag === 'resource' ) {
1310 // resource is for url.
1311 // value attribute is a weird way of just putting the contents.
1312 $this->char( $this->xmlParser, $val );
1313 }
1314 } elseif ( isset( $this->items[$ns][$tag] ) ) {
1315 if ( $this->mode[0] === self::MODE_SIMPLE ) {
1316 throw new RuntimeException( __METHOD__
1317 . " $ns:$tag found as attribute where not allowed" );
1318 }
1319 $this->saveValue( $ns, $tag, $val );
1320 } else {
1321 $this->logger->debug( __METHOD__ . " Ignoring unrecognized element <$ns:$tag>." );
1322 }
1323 }
1324 }
1325
1337 private function saveValue( $ns, $tag, $val ) {
1338
1339 $info =& $this->items[$ns][$tag];
1340 $finalName = isset( $info['map_name'] )
1341 ? $info['map_name'] : $tag;
1342 if ( isset( $info['validate'] ) ) {
1343 if ( is_array( $info['validate'] ) ) {
1344 $validate = $info['validate'];
1345 } else {
1346 $validator = new XMPValidate( $this->logger );
1347 $validate = [ $validator, $info['validate'] ];
1348 }
1349
1350 if ( is_callable( $validate ) ) {
1351 call_user_func_array( $validate, [ $info, &$val, true ] );
1352 // the reasoning behind using &$val instead of using the return value
1353 // is to be consistent between here and validating structures.
1354 if ( is_null( $val ) ) {
1355 $this->logger->info( __METHOD__ . " <$ns:$tag> failed validation." );
1356
1357 return;
1358 }
1359 } else {
1360 $this->logger->warning( __METHOD__ . " Validation function for $finalName ("
1361 . $validate[0] . '::' . $validate[1] . '()) is not callable.' );
1362 }
1363 }
1364
1365 if ( $this->ancestorStruct && $this->processingArray ) {
1366 // Aka both an array and a struct. ( self::MODE_BAGSTRUCT )
1367 $this->results['xmp-' . $info['map_group']][$this->ancestorStruct][][$finalName] = $val;
1368 } elseif ( $this->ancestorStruct ) {
1369 $this->results['xmp-' . $info['map_group']][$this->ancestorStruct][$finalName] = $val;
1370 } elseif ( $this->processingArray ) {
1371 if ( $this->itemLang === false ) {
1372 // normal array
1373 $this->results['xmp-' . $info['map_group']][$finalName][] = $val;
1374 } else {
1375 // lang array.
1376 $this->results['xmp-' . $info['map_group']][$finalName][$this->itemLang] = $val;
1377 }
1378 } else {
1379 $this->results['xmp-' . $info['map_group']][$finalName] = $val;
1380 }
1381 }
1382}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
$line
Definition cdb.php:58
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:53
const NS_XML
Definition XMP.php:119
const NS_RDF
Definition XMP.php:118
bool string $charContent
Temporary holder for character data that appears in xmp doc.
Definition XMP.php:64
const MODE_SEQ
Definition XMP.php:112
const MODE_LANG
Definition XMP.php:114
bool string $itemLang
Used for lang alts only.
Definition XMP.php:76
destroyXMLParser()
free the XML parser.
Definition XMP.php:165
const MODE_STRUCT
Definition XMP.php:111
const PARSABLE_BUFFERING
Definition XMP.php:124
LoggerInterface $logger
Definition XMP.php:130
const PARSABLE_NO
Definition XMP.php:125
const PARSABLE_OK
Definition XMP.php:123
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:590
const MODE_LI
Definition XMP.php:104
char( $parser, $data)
Character data handler Called whenever character data is found in the xmp document.
Definition XMP.php:498
setLogger(LoggerInterface $logger)
Definition XMP.php:154
getResults()
Get the result array.
Definition XMP.php:207
startElementModeLang( $elm)
Start element in MODE_LANG (language alternative) this should always be <rdf:Alt>
Definition XMP.php:921
array $results
Array to hold results.
Definition XMP.php:70
bool string $charset
Character set like 'UTF-8'.
Definition XMP.php:82
doAttribs( $attribs)
Process attributes.
Definition XMP.php:1288
parseExtended( $content)
Entry point for XMPExtended blocks in jpeg files.
Definition XMP.php:418
endElementModeSimple( $elm)
Hit a closing element when in MODE_SIMPLE.
Definition XMP.php:612
array $curItem
Array to hold the current element (and previous element, and so on)
Definition XMP.php:58
const PARSABLE_UNKNOWN
Definition XMP.php:122
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:1067
static isSupported()
Check if this instance supports using this class.
Definition XMP.php:197
parse( $content, $allOfIt=true)
Main function to call to parse XMP.
Definition XMP.php:302
array $mode
Stores the state the xmpreader is in (see MODE_FOO constants)
Definition XMP.php:67
int $parsable
Flag determining if the XMP is safe to parse.
Definition XMP.php:88
resetXMLParser()
Main use is if a single item has multiple xmp documents describing it.
Definition XMP.php:176
startElement( $parser, $elm, $attribs)
Hits an opening element.
Definition XMP.php:1196
startElementModeBag( $elm)
Start element in MODE_BAG (unordered array) this should always be <rdf:Bag>
Definition XMP.php:879
const MODE_INITIAL
These are various mode constants.
Definition XMP.php:102
const MODE_ALT
Definition XMP.php:115
startElementModeQDesc( $elm)
Start an element when in MODE_QDESC.
Definition XMP.php:983
__construct(LoggerInterface $logger=null)
Constructor.
Definition XMP.php:137
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:1005
const MODE_LI_LANG
Definition XMP.php:105
saveValue( $ns, $tag, $val)
Given an extracted value, save it to results array.
Definition XMP.php:1337
endElement( $parser, $elm)
Handler for hitting a closing element.
Definition XMP.php:783
startElementModeSeq( $elm)
Start element in MODE_SEQ (ordered array) this should always be <rdf:Seq>
Definition XMP.php:894
string $xmlParsableBuffer
Buffer of XML to parse.
Definition XMP.php:91
const MODE_QDESC
Definition XMP.php:106
const MODE_SIMPLE
Definition XMP.php:110
const MODE_BAG
Definition XMP.php:113
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:717
array $items
XMP item configuration array.
Definition XMP.php:55
startElementModeSimple( $elm, $attribs)
Handle an opening element when in MODE_SIMPLE.
Definition XMP.php:947
const MODE_BAGSTRUCT
Definition XMP.php:116
resource $xmlParser
A resource handle for the XML parser.
Definition XMP.php:79
endElementNested( $elm)
Hit a closing element in MODE_STRUCT, MODE_SEQ, MODE_BAG generally means we've finished processing a ...
Definition XMP.php:647
startElementModeIgnore( $elm)
Hit an opening element while in MODE_IGNORE.
Definition XMP.php:865
startElementModeLi( $elm, $attribs)
opening element in MODE_LI process elements of arrays.
Definition XMP.php:1115
bool $processingArray
If we're doing a seq or bag.
Definition XMP.php:73
const MODE_IGNORE
Definition XMP.php:103
bool string $ancestorStruct
The structure name when processing nested structures.
Definition XMP.php:61
int $extendedXMPOffset
Definition XMP.php:85
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:757
checkParseSafety( $content)
Check if a block of XML is safe to pass to xml_parse, i.e.
Definition XMP.php:535
startElementModeLiLang( $elm, $attribs)
Opening element in MODE_LI_LANG.
Definition XMP.php:1165
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.
do that in ParserLimitReportFormat instead $parser
Definition hooks.txt:2536
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:Array with elements of the form "language:title" in the order that they will be output. & $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:1954
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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:1100
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:865
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books $tag
Definition hooks.txt:1033
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:304
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:1975
returning false will NOT prevent logging $e
Definition hooks.txt:2127
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