MediaWiki  1.28.3
XMP.php
Go to the documentation of this file.
1 <?php
28 
54 class 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
123  const PARSABLE_UNKNOWN = 0;
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] ) {
822  case self::MODE_IGNORE:
823  $this->endElementModeIgnore( $elm );
824  break;
825  case self::MODE_SIMPLE:
826  $this->endElementModeSimple( $elm );
827  break;
828  case self::MODE_STRUCT:
829  case self::MODE_SEQ:
830  case self::MODE_BAG:
831  case self::MODE_LANG:
832  case self::MODE_BAGSTRUCT:
833  $this->endElementNested( $elm );
834  break;
835  case self::MODE_INITIAL:
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:
843  case self::MODE_LI_LANG:
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:
1250  case self::MODE_BAGSTRUCT:
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 }
doAttribs($attribs)
Process attributes.
Definition: XMP.php:1290
startElementModeLi($elm, $attribs)
opening element in MODE_LI process elements of arrays.
Definition: XMP.php:1117
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
int $extendedXMPOffset
Definition: XMP.php:86
startElement($parser, $elm, $attribs)
Hits an opening element.
Definition: XMP.php:1198
checkParseSafety($content)
Check if a block of XML is safe to pass to xml_parse, i.e.
Definition: XMP.php:536
const MODE_LI
Definition: XMP.php:105
const MODE_INITIAL
These are various mode constants.
Definition: XMP.php:103
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException'returning false will NOT prevent logging $e
Definition: hooks.txt:2106
const PARSABLE_BUFFERING
Definition: XMP.php:125
destroyXMLParser()
free the XML parser.
Definition: XMP.php:166
string $xmlParsableBuffer
Buffer of XML to parse.
Definition: XMP.php:92
parseExtended($content)
Entry point for XMPExtended blocks in jpeg files.
Definition: XMP.php:419
getResults()
Get the result array.
Definition: XMP.php:208
const MODE_SEQ
Definition: XMP.php:113
int $parsable
Flag determining if the XMP is safe to parse.
Definition: XMP.php:89
endElement($parser, $elm)
Handler for hitting a closing element.
Definition: XMP.php:784
magic word & $parser
Definition: hooks.txt:2491
char($parser, $data)
Character data handler Called whenever character data is found in the xmp document.
Definition: XMP.php:499
bool string $itemLang
Used for lang alts only.
Definition: XMP.php:77
bool string $ancestorStruct
The structure name when processing nested structures.
Definition: XMP.php:62
static getItems()
Get the items array.
Definition: XMPInfo.php:33
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:1938
endElementNested($elm)
Hit a closing element in MODE_STRUCT, MODE_SEQ, MODE_BAG generally means we've finished processing a ...
Definition: XMP.php:648
array $results
Array to hold results.
Definition: XMP.php:71
static isSupported()
Check if this instance supports using this class.
Definition: XMP.php:198
const PARSABLE_UNKNOWN
Definition: XMP.php:123
LoggerInterface $logger
Definition: XMP.php:131
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 PARSABLE_NO
Definition: XMP.php:126
startElementModeBag($elm)
Start element in MODE_BAG (unordered array) this should always be
Definition: XMP.php:880
startElementModeSeq($elm)
Start element in MODE_SEQ (ordered array) this should always be
Definition: XMP.php:895
saveValue($ns, $tag, $val)
Given an extracted value, save it to results array.
Definition: XMP.php:1339
const MODE_LANG
Definition: XMP.php:115
const MODE_BAG
Definition: XMP.php:114
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:1940
const MODE_LI_LANG
Definition: XMP.php:106
bool string $charContent
Temporary holder for character data that appears in xmp doc.
Definition: XMP.php:65
startElementModeQDesc($elm)
Start an element when in MODE_QDESC.
Definition: XMP.php:985
const MODE_QDESC
Definition: XMP.php:107
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:1011
This contains some static methods for validating XMP properties.
Definition: XMPValidate.php:46
const PARSABLE_OK
Definition: XMP.php:124
const MODE_IGNORE
Definition: XMP.php:104
const MODE_BAGSTRUCT
Definition: XMP.php:117
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
startElementModeLiLang($elm, $attribs)
Opening element in MODE_LI_LANG.
Definition: XMP.php:1167
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
Definition: distributors.txt:9
Class for reading xmp data containing properties relevant to images, and spitting out an array that F...
Definition: XMP.php:54
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:806
array $curItem
Array to hold the current element (and previous element, and so on)
Definition: XMP.php:59
startElementModeIgnore($elm)
Hit an opening element while in MODE_IGNORE.
Definition: XMP.php:866
parse($content, $allOfIt=true)
Main function to call to parse XMP.
Definition: XMP.php:303
resetXMLParser()
Main use is if a single item has multiple xmp documents describing it.
Definition: XMP.php:177
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:35
bool string $charset
Character set like 'UTF-8'.
Definition: XMP.php:83
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
resource $xmlParser
A resource handle for the XML parser.
Definition: XMP.php:80
endElementModeSimple($elm)
Hit a closing element when in MODE_SIMPLE.
Definition: XMP.php:613
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:1050
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
$line
Definition: cdb.php:59
setLogger(LoggerInterface $logger)
Definition: XMP.php:155
const MODE_ALT
Definition: XMP.php:116
array $items
XMP item configuration array.
Definition: XMP.php:56
const NS_XML
Definition: XMP.php:120
const MODE_SIMPLE
Definition: XMP.php:111
startElementModeLang($elm)
Start element in MODE_LANG (language alternative) this should always be ...
Definition: XMP.php:922
array $mode
Stores the state the xmpreader is in (see MODE_FOO constants)
Definition: XMP.php:68
__construct(LoggerInterface $logger=null)
Constructor.
Definition: XMP.php:138
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
startElementModeSimple($elm, $attribs)
Handle an opening element when in MODE_SIMPLE.
Definition: XMP.php:948
const MODE_STRUCT
Definition: XMP.php:112
bool $processingArray
If we're doing a seq or bag.
Definition: XMP.php:74
const NS_RDF
Definition: XMP.php:119
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:304