MediaWiki  1.27.2
XMP.php
Go to the documentation of this file.
1 <?php
27 
53 class 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
122  const PARSABLE_UNKNOWN = 0;
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] ) {
821  case self::MODE_IGNORE:
822  $this->endElementModeIgnore( $elm );
823  break;
824  case self::MODE_SIMPLE:
825  $this->endElementModeSimple( $elm );
826  break;
827  case self::MODE_STRUCT:
828  case self::MODE_SEQ:
829  case self::MODE_BAG:
830  case self::MODE_LANG:
831  case self::MODE_BAGSTRUCT:
832  $this->endElementNested( $elm );
833  break;
834  case self::MODE_INITIAL:
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:
842  case self::MODE_LI_LANG:
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  # bug 27105
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 
984  private function startElementModeQDesc( $elm ) {
985  if ( $elm === self::NS_RDF . ' value' ) {
986  return; // do nothing
987  } else {
988  // otherwise its a qualifier, which we ignore
989  array_unshift( $this->mode, self::MODE_IGNORE );
990  array_unshift( $this->curItem, $elm );
991  }
992  }
993 
1006  private function startElementModeInitial( $ns, $tag, $attribs ) {
1007  if ( $ns !== self::NS_RDF ) {
1008 
1009  if ( isset( $this->items[$ns][$tag] ) ) {
1010  if ( isset( $this->items[$ns][$tag]['structPart'] ) ) {
1011  // If this element is supposed to appear only as
1012  // a child of a structure, but appears here (not as
1013  // a child of a struct), then something weird is
1014  // happening, so ignore this element and its children.
1015 
1016  $this->logger->warning( "Encountered <$ns:$tag> outside"
1017  . " of its expected parent. Ignoring." );
1018 
1019  array_unshift( $this->mode, self::MODE_IGNORE );
1020  array_unshift( $this->curItem, $ns . ' ' . $tag );
1021 
1022  return;
1023  }
1024  $mode = $this->items[$ns][$tag]['mode'];
1025  array_unshift( $this->mode, $mode );
1026  array_unshift( $this->curItem, $ns . ' ' . $tag );
1027  if ( $mode === self::MODE_STRUCT ) {
1028  $this->ancestorStruct = isset( $this->items[$ns][$tag]['map_name'] )
1029  ? $this->items[$ns][$tag]['map_name'] : $tag;
1030  }
1031  if ( $this->charContent !== false ) {
1032  // Something weird.
1033  // Should not happen in valid XMP.
1034  throw new RuntimeException( 'tag nested in non-whitespace characters.' );
1035  }
1036  } else {
1037  // This element is not on our list of allowed elements so ignore.
1038  $this->logger->debug( __METHOD__ . " Ignoring unrecognized element <$ns:$tag>." );
1039  array_unshift( $this->mode, self::MODE_IGNORE );
1040  array_unshift( $this->curItem, $ns . ' ' . $tag );
1041 
1042  return;
1043  }
1044  }
1045  // process attributes
1046  $this->doAttribs( $attribs );
1047  }
1048 
1068  private function startElementModeStruct( $ns, $tag, $attribs ) {
1069  if ( $ns !== self::NS_RDF ) {
1070 
1071  if ( isset( $this->items[$ns][$tag] ) ) {
1072  if ( isset( $this->items[$ns][$this->ancestorStruct]['children'] )
1073  && !isset( $this->items[$ns][$this->ancestorStruct]['children'][$tag] )
1074  ) {
1075  // This assumes that we don't have inter-namespace nesting
1076  // which we don't in all the properties we're interested in.
1077  throw new RuntimeException( " <$tag> appeared nested in <" . $this->ancestorStruct
1078  . "> where it is not allowed." );
1079  }
1080  array_unshift( $this->mode, $this->items[$ns][$tag]['mode'] );
1081  array_unshift( $this->curItem, $ns . ' ' . $tag );
1082  if ( $this->charContent !== false ) {
1083  // Something weird.
1084  // Should not happen in valid XMP.
1085  throw new RuntimeException( "tag <$tag> nested in non-whitespace characters (" .
1086  $this->charContent . ")." );
1087  }
1088  } else {
1089  array_unshift( $this->mode, self::MODE_IGNORE );
1090  array_unshift( $this->curItem, $elm );
1091 
1092  return;
1093  }
1094  }
1095 
1096  if ( $ns === self::NS_RDF && $tag === 'Description' ) {
1097  $this->doAttribs( $attribs );
1098  array_unshift( $this->mode, self::MODE_STRUCT );
1099  array_unshift( $this->curItem, $this->curItem[0] );
1100  }
1101  }
1102 
1116  private function startElementModeLi( $elm, $attribs ) {
1117  if ( ( $elm ) !== self::NS_RDF . ' li' ) {
1118  throw new RuntimeException( "<rdf:li> expected but got $elm." );
1119  }
1120 
1121  if ( !isset( $this->mode[1] ) ) {
1122  // This should never ever ever happen. Checking for it
1123  // to be paranoid.
1124  throw new RuntimeException( 'In mode Li, but no 2xPrevious mode!' );
1125  }
1126 
1127  if ( $this->mode[1] === self::MODE_BAGSTRUCT ) {
1128  // This list item contains a compound (STRUCT) value.
1129  array_unshift( $this->mode, self::MODE_STRUCT );
1130  array_unshift( $this->curItem, $elm );
1131  $this->processingArray = true;
1132 
1133  if ( !isset( $this->curItem[1] ) ) {
1134  // be paranoid.
1135  throw new RuntimeException( 'Can not find parent of BAGSTRUCT.' );
1136  }
1137  list( $curNS, $curTag ) = explode( ' ', $this->curItem[1] );
1138  $this->ancestorStruct = isset( $this->items[$curNS][$curTag]['map_name'] )
1139  ? $this->items[$curNS][$curTag]['map_name'] : $curTag;
1140 
1141  $this->doAttribs( $attribs );
1142  } else {
1143  // Normal BAG or SEQ containing simple values.
1144  array_unshift( $this->mode, self::MODE_SIMPLE );
1145  // need to add curItem[0] on again since one is for the specific item
1146  // and one is for the entire group.
1147  array_unshift( $this->curItem, $this->curItem[0] );
1148  $this->processingArray = true;
1149  }
1150  }
1151 
1166  private function startElementModeLiLang( $elm, $attribs ) {
1167  if ( $elm !== self::NS_RDF . ' li' ) {
1168  throw new RuntimeException( __METHOD__ . " <rdf:li> expected but got $elm." );
1169  }
1170  if ( !isset( $attribs[self::NS_XML . ' lang'] )
1171  || !preg_match( '/^[-A-Za-z0-9]{2,}$/D', $attribs[self::NS_XML . ' lang'] )
1172  ) {
1173  throw new RuntimeException( __METHOD__
1174  . " <rdf:li> did not contain, or has invalid xml:lang attribute in lang alternative" );
1175  }
1176 
1177  // Lang is case-insensitive.
1178  $this->itemLang = strtolower( $attribs[self::NS_XML . ' lang'] );
1179 
1180  // need to add curItem[0] on again since one is for the specific item
1181  // and one is for the entire group.
1182  array_unshift( $this->curItem, $this->curItem[0] );
1183  array_unshift( $this->mode, self::MODE_SIMPLE );
1184  $this->processingArray = true;
1185  }
1186 
1197  function startElement( $parser, $elm, $attribs ) {
1198 
1199  if ( $elm === self::NS_RDF . ' RDF'
1200  || $elm === 'adobe:ns:meta/ xmpmeta'
1201  || $elm === 'adobe:ns:meta/ xapmeta'
1202  ) {
1203  /* ignore. */
1204  return;
1205  } elseif ( $elm === self::NS_RDF . ' Description' ) {
1206  if ( count( $this->mode ) === 0 ) {
1207  // outer rdf:desc
1208  array_unshift( $this->mode, self::MODE_INITIAL );
1209  }
1210  } elseif ( $elm === self::NS_RDF . ' type' ) {
1211  // This doesn't support rdf:type properly.
1212  // In practise I have yet to see a file that
1213  // uses this element, however it is mentioned
1214  // on page 25 of part 1 of the xmp standard.
1215  // Also it seems as if exiv2 and exiftool do not support
1216  // this either (That or I misunderstand the standard)
1217  $this->logger->info( __METHOD__ . ' Encountered <rdf:type> which isn\'t currently supported' );
1218  }
1219 
1220  if ( strpos( $elm, ' ' ) === false ) {
1221  // This probably shouldn't happen.
1222  $this->logger->info( __METHOD__ . " Encountered <$elm> which has no namespace. Skipping." );
1223 
1224  return;
1225  }
1226 
1227  list( $ns, $tag ) = explode( ' ', $elm, 2 );
1228 
1229  if ( count( $this->mode ) === 0 ) {
1230  // This should not happen.
1231  throw new RuntimeException( 'Error extracting XMP, '
1232  . "encountered <$elm> with no mode" );
1233  }
1234 
1235  switch ( $this->mode[0] ) {
1236  case self::MODE_IGNORE:
1237  $this->startElementModeIgnore( $elm );
1238  break;
1239  case self::MODE_SIMPLE:
1240  $this->startElementModeSimple( $elm, $attribs );
1241  break;
1242  case self::MODE_INITIAL:
1243  $this->startElementModeInitial( $ns, $tag, $attribs );
1244  break;
1245  case self::MODE_STRUCT:
1246  $this->startElementModeStruct( $ns, $tag, $attribs );
1247  break;
1248  case self::MODE_BAG:
1249  case self::MODE_BAGSTRUCT:
1250  $this->startElementModeBag( $elm );
1251  break;
1252  case self::MODE_SEQ:
1253  $this->startElementModeSeq( $elm );
1254  break;
1255  case self::MODE_LANG:
1256  $this->startElementModeLang( $elm );
1257  break;
1258  case self::MODE_LI_LANG:
1259  $this->startElementModeLiLang( $elm, $attribs );
1260  break;
1261  case self::MODE_LI:
1262  $this->startElementModeLi( $elm, $attribs );
1263  break;
1264  case self::MODE_QDESC:
1265  $this->startElementModeQDesc( $elm );
1266  break;
1267  default:
1268  throw new RuntimeException( 'StartElement in unknown mode: ' . $this->mode[0] );
1269  }
1270  }
1271 
1272  // @codingStandardsIgnoreStart Generic.Files.LineLength
1288  // @codingStandardsIgnoreEnd
1289  private function doAttribs( $attribs ) {
1290  // first check for rdf:parseType attribute, as that can change
1291  // how the attributes are interperted.
1292 
1293  if ( isset( $attribs[self::NS_RDF . ' parseType'] )
1294  && $attribs[self::NS_RDF . ' parseType'] === 'Resource'
1295  && $this->mode[0] === self::MODE_SIMPLE
1296  ) {
1297  // this is equivalent to having an inner rdf:Description
1298  $this->mode[0] = self::MODE_QDESC;
1299  }
1300  foreach ( $attribs as $name => $val ) {
1301  if ( strpos( $name, ' ' ) === false ) {
1302  // This shouldn't happen, but so far some old software forgets namespace
1303  // on rdf:about.
1304  $this->logger->info( __METHOD__ . ' Encountered non-namespaced attribute: '
1305  . " $name=\"$val\". Skipping. " );
1306  continue;
1307  }
1308  list( $ns, $tag ) = explode( ' ', $name, 2 );
1309  if ( $ns === self::NS_RDF ) {
1310  if ( $tag === 'value' || $tag === 'resource' ) {
1311  // resource is for url.
1312  // value attribute is a weird way of just putting the contents.
1313  $this->char( $this->xmlParser, $val );
1314  }
1315  } elseif ( isset( $this->items[$ns][$tag] ) ) {
1316  if ( $this->mode[0] === self::MODE_SIMPLE ) {
1317  throw new RuntimeException( __METHOD__
1318  . " $ns:$tag found as attribute where not allowed" );
1319  }
1320  $this->saveValue( $ns, $tag, $val );
1321  } else {
1322  $this->logger->debug( __METHOD__ . " Ignoring unrecognized element <$ns:$tag>." );
1323  }
1324  }
1325  }
1326 
1338  private function saveValue( $ns, $tag, $val ) {
1339 
1340  $info =& $this->items[$ns][$tag];
1341  $finalName = isset( $info['map_name'] )
1342  ? $info['map_name'] : $tag;
1343  if ( isset( $info['validate'] ) ) {
1344  if ( is_array( $info['validate'] ) ) {
1345  $validate = $info['validate'];
1346  } else {
1347  $validator = new XMPValidate( $this->logger );
1348  $validate = [ $validator, $info['validate'] ];
1349  }
1350 
1351  if ( is_callable( $validate ) ) {
1352  call_user_func_array( $validate, [ $info, &$val, true ] );
1353  // the reasoning behind using &$val instead of using the return value
1354  // is to be consistent between here and validating structures.
1355  if ( is_null( $val ) ) {
1356  $this->logger->info( __METHOD__ . " <$ns:$tag> failed validation." );
1357 
1358  return;
1359  }
1360  } else {
1361  $this->logger->warning( __METHOD__ . " Validation function for $finalName ("
1362  . $validate[0] . '::' . $validate[1] . '()) is not callable.' );
1363  }
1364  }
1365 
1366  if ( $this->ancestorStruct && $this->processingArray ) {
1367  // Aka both an array and a struct. ( self::MODE_BAGSTRUCT )
1368  $this->results['xmp-' . $info['map_group']][$this->ancestorStruct][][$finalName] = $val;
1369  } elseif ( $this->ancestorStruct ) {
1370  $this->results['xmp-' . $info['map_group']][$this->ancestorStruct][$finalName] = $val;
1371  } elseif ( $this->processingArray ) {
1372  if ( $this->itemLang === false ) {
1373  // normal array
1374  $this->results['xmp-' . $info['map_group']][$finalName][] = $val;
1375  } else {
1376  // lang array.
1377  $this->results['xmp-' . $info['map_group']][$finalName][$this->itemLang] = $val;
1378  }
1379  } else {
1380  $this->results['xmp-' . $info['map_group']][$finalName] = $val;
1381  }
1382  }
1383 }
doAttribs($attribs)
Process attributes.
Definition: XMP.php:1289
startElementModeLi($elm, $attribs)
opening element in MODE_LI process elements of arrays.
Definition: XMP.php:1116
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:85
startElement($parser, $elm, $attribs)
Hits an opening element.
Definition: XMP.php:1197
checkParseSafety($content)
Check if a block of XML is safe to pass to xml_parse, i.e.
Definition: XMP.php:535
const MODE_LI
Definition: XMP.php:104
const MODE_INITIAL
These are various mode constants.
Definition: XMP.php:102
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:1932
const PARSABLE_BUFFERING
Definition: XMP.php:124
destroyXMLParser()
free the XML parser.
Definition: XMP.php:165
string $xmlParsableBuffer
Buffer of XML to parse.
Definition: XMP.php:91
parseExtended($content)
Entry point for XMPExtended blocks in jpeg files.
Definition: XMP.php:418
getResults()
Get the result array.
Definition: XMP.php:207
const MODE_SEQ
Definition: XMP.php:112
int $parsable
Flag determining if the XMP is safe to parse.
Definition: XMP.php:88
endElement($parser, $elm)
Handler for hitting a closing element.
Definition: XMP.php:783
magic word & $parser
Definition: hooks.txt:2321
char($parser, $data)
Character data handler Called whenever character data is found in the xmp document.
Definition: XMP.php:498
bool string $itemLang
Used for lang alts only.
Definition: XMP.php:76
bool string $ancestorStruct
The structure name when processing nested structures.
Definition: XMP.php:61
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':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:1796
endElementNested($elm)
Hit a closing element in MODE_STRUCT, MODE_SEQ, MODE_BAG generally means we've finished processing a ...
Definition: XMP.php:647
Class for asserting that a callback happens when an dummy object leaves scope.
array $results
Array to hold results.
Definition: XMP.php:70
static isSupported()
Check if this instance supports using this class.
Definition: XMP.php:197
const PARSABLE_UNKNOWN
Definition: XMP.php:122
LoggerInterface $logger
Definition: XMP.php:130
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 PARSABLE_NO
Definition: XMP.php:125
startElementModeBag($elm)
Start element in MODE_BAG (unordered array) this should always be
Definition: XMP.php:879
startElementModeSeq($elm)
Start element in MODE_SEQ (ordered array) this should always be
Definition: XMP.php:894
saveValue($ns, $tag, $val)
Given an extracted value, save it to results array.
Definition: XMP.php:1338
const MODE_LANG
Definition: XMP.php:114
const MODE_BAG
Definition: XMP.php:113
const MODE_LI_LANG
Definition: XMP.php:105
bool string $charContent
Temporary holder for character data that appears in xmp doc.
Definition: XMP.php:64
startElementModeQDesc($elm)
Start an element when in MODE_QDESC.
Definition: XMP.php:984
const MODE_QDESC
Definition: XMP.php:106
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:965
This contains some static methods for validating XMP properties.
Definition: XMPValidate.php:46
const PARSABLE_OK
Definition: XMP.php:123
const MODE_IGNORE
Definition: XMP.php:103
const MODE_BAGSTRUCT
Definition: XMP.php:116
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
startElementModeLiLang($elm, $attribs)
Opening element in MODE_LI_LANG.
Definition: XMP.php:1166
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:53
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:762
array $curItem
Array to hold the current element (and previous element, and so on)
Definition: XMP.php:58
startElementModeIgnore($elm)
Hit an opening element while in MODE_IGNORE.
Definition: XMP.php:865
parse($content, $allOfIt=true)
Main function to call to parse XMP.
Definition: XMP.php:302
resetXMLParser()
Main use is if a single item has multiple xmp documents describing it.
Definition: XMP.php:176
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:82
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:1006
resource $xmlParser
A resource handle for the XML parser.
Definition: XMP.php:79
endElementModeSimple($elm)
Hit a closing element when in MODE_SIMPLE.
Definition: XMP.php:612
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:1004
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:1068
$line
Definition: cdb.php:59
setLogger(LoggerInterface $logger)
Definition: XMP.php:154
const MODE_ALT
Definition: XMP.php:115
array $items
XMP item configuration array.
Definition: XMP.php:55
const NS_XML
Definition: XMP.php:119
const MODE_SIMPLE
Definition: XMP.php:110
startElementModeLang($elm)
Start element in MODE_LANG (language alternative) this should always be ...
Definition: XMP.php:921
array $mode
Stores the state the xmpreader is in (see MODE_FOO constants)
Definition: XMP.php:67
__construct(LoggerInterface $logger=null)
Constructor.
Definition: XMP.php:137
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:1798
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
startElementModeSimple($elm, $attribs)
Handle an opening element when in MODE_SIMPLE.
Definition: XMP.php:947
const MODE_STRUCT
Definition: XMP.php:111
bool $processingArray
If we're doing a seq or bag.
Definition: XMP.php:73
const NS_RDF
Definition: XMP.php:118
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:310