MediaWiki  1.29.2
XMP.php
Go to the documentation of this file.
1 <?php
24 use Psr\Log\LoggerAwareInterface;
25 use Psr\Log\LoggerInterface;
26 use Psr\Log\NullLogger;
27 use Wikimedia\ScopedCallback;
28 
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:
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  # T29105
899  $this->logger->info( __METHOD__ . ' Expected an rdf:Seq, but got an rdf:Bag. Pretending'
900  . ' it is a Seq, since some buggy software is known to screw this up.' );
901  array_unshift( $this->mode, self::MODE_LI );
902  } else {
903  throw new RuntimeException( "Expected <rdf:Seq> but got $elm." );
904  }
905  }
906 
921  private function startElementModeLang( $elm ) {
922  if ( $elm === self::NS_RDF . ' Alt' ) {
923  array_unshift( $this->mode, self::MODE_LI_LANG );
924  } else {
925  throw new RuntimeException( "Expected <rdf:Seq> but got $elm." );
926  }
927  }
928 
947  private function startElementModeSimple( $elm, $attribs ) {
948  if ( $elm === self::NS_RDF . ' Description' ) {
949  // If this value has qualifiers
950  array_unshift( $this->mode, self::MODE_QDESC );
951  array_unshift( $this->curItem, $this->curItem[0] );
952 
953  if ( isset( $attribs[self::NS_RDF . ' value'] ) ) {
954  list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
955  $this->saveValue( $ns, $tag, $attribs[self::NS_RDF . ' value'] );
956  }
957  } elseif ( $elm === self::NS_RDF . ' value' ) {
958  // This should not be here.
959  throw new RuntimeException( __METHOD__ . ' Encountered <rdf:value> where it was unexpected.' );
960  } else {
961  // something else we don't recognize, like a qualifier maybe.
962  $this->logger->info( __METHOD__ .
963  " Encountered element <$elm> where only expecting character data as value of " .
964  $this->curItem[0] );
965  array_unshift( $this->mode, self::MODE_IGNORE );
966  array_unshift( $this->curItem, $elm );
967  }
968  }
969 
983  private function startElementModeQDesc( $elm ) {
984  if ( $elm === self::NS_RDF . ' value' ) {
985  return; // do nothing
986  } else {
987  // otherwise its a qualifier, which we ignore
988  array_unshift( $this->mode, self::MODE_IGNORE );
989  array_unshift( $this->curItem, $elm );
990  }
991  }
992 
1005  private function startElementModeInitial( $ns, $tag, $attribs ) {
1006  if ( $ns !== self::NS_RDF ) {
1007 
1008  if ( isset( $this->items[$ns][$tag] ) ) {
1009  if ( isset( $this->items[$ns][$tag]['structPart'] ) ) {
1010  // If this element is supposed to appear only as
1011  // a child of a structure, but appears here (not as
1012  // a child of a struct), then something weird is
1013  // happening, so ignore this element and its children.
1014 
1015  $this->logger->warning( "Encountered <$ns:$tag> outside"
1016  . " of its expected parent. Ignoring." );
1017 
1018  array_unshift( $this->mode, self::MODE_IGNORE );
1019  array_unshift( $this->curItem, $ns . ' ' . $tag );
1020 
1021  return;
1022  }
1023  $mode = $this->items[$ns][$tag]['mode'];
1024  array_unshift( $this->mode, $mode );
1025  array_unshift( $this->curItem, $ns . ' ' . $tag );
1026  if ( $mode === self::MODE_STRUCT ) {
1027  $this->ancestorStruct = isset( $this->items[$ns][$tag]['map_name'] )
1028  ? $this->items[$ns][$tag]['map_name'] : $tag;
1029  }
1030  if ( $this->charContent !== false ) {
1031  // Something weird.
1032  // Should not happen in valid XMP.
1033  throw new RuntimeException( 'tag nested in non-whitespace characters.' );
1034  }
1035  } else {
1036  // This element is not on our list of allowed elements so ignore.
1037  $this->logger->debug( __METHOD__ . " Ignoring unrecognized element <$ns:$tag>." );
1038  array_unshift( $this->mode, self::MODE_IGNORE );
1039  array_unshift( $this->curItem, $ns . ' ' . $tag );
1040 
1041  return;
1042  }
1043  }
1044  // process attributes
1045  $this->doAttribs( $attribs );
1046  }
1047 
1067  private function startElementModeStruct( $ns, $tag, $attribs ) {
1068  if ( $ns !== self::NS_RDF ) {
1069 
1070  if ( isset( $this->items[$ns][$tag] ) ) {
1071  if ( isset( $this->items[$ns][$this->ancestorStruct]['children'] )
1072  && !isset( $this->items[$ns][$this->ancestorStruct]['children'][$tag] )
1073  ) {
1074  // This assumes that we don't have inter-namespace nesting
1075  // which we don't in all the properties we're interested in.
1076  throw new RuntimeException( " <$tag> appeared nested in <" . $this->ancestorStruct
1077  . "> where it is not allowed." );
1078  }
1079  array_unshift( $this->mode, $this->items[$ns][$tag]['mode'] );
1080  array_unshift( $this->curItem, $ns . ' ' . $tag );
1081  if ( $this->charContent !== false ) {
1082  // Something weird.
1083  // Should not happen in valid XMP.
1084  throw new RuntimeException( "tag <$tag> nested in non-whitespace characters (" .
1085  $this->charContent . ")." );
1086  }
1087  } else {
1088  array_unshift( $this->mode, self::MODE_IGNORE );
1089  array_unshift( $this->curItem, $ns . ' ' . $tag );
1090 
1091  return;
1092  }
1093  }
1094 
1095  if ( $ns === self::NS_RDF && $tag === 'Description' ) {
1096  $this->doAttribs( $attribs );
1097  array_unshift( $this->mode, self::MODE_STRUCT );
1098  array_unshift( $this->curItem, $this->curItem[0] );
1099  }
1100  }
1101 
1115  private function startElementModeLi( $elm, $attribs ) {
1116  if ( ( $elm ) !== self::NS_RDF . ' li' ) {
1117  throw new RuntimeException( "<rdf:li> expected but got $elm." );
1118  }
1119 
1120  if ( !isset( $this->mode[1] ) ) {
1121  // This should never ever ever happen. Checking for it
1122  // to be paranoid.
1123  throw new RuntimeException( 'In mode Li, but no 2xPrevious mode!' );
1124  }
1125 
1126  if ( $this->mode[1] === self::MODE_BAGSTRUCT ) {
1127  // This list item contains a compound (STRUCT) value.
1128  array_unshift( $this->mode, self::MODE_STRUCT );
1129  array_unshift( $this->curItem, $elm );
1130  $this->processingArray = true;
1131 
1132  if ( !isset( $this->curItem[1] ) ) {
1133  // be paranoid.
1134  throw new RuntimeException( 'Can not find parent of BAGSTRUCT.' );
1135  }
1136  list( $curNS, $curTag ) = explode( ' ', $this->curItem[1] );
1137  $this->ancestorStruct = isset( $this->items[$curNS][$curTag]['map_name'] )
1138  ? $this->items[$curNS][$curTag]['map_name'] : $curTag;
1139 
1140  $this->doAttribs( $attribs );
1141  } else {
1142  // Normal BAG or SEQ containing simple values.
1143  array_unshift( $this->mode, self::MODE_SIMPLE );
1144  // need to add curItem[0] on again since one is for the specific item
1145  // and one is for the entire group.
1146  array_unshift( $this->curItem, $this->curItem[0] );
1147  $this->processingArray = true;
1148  }
1149  }
1150 
1165  private function startElementModeLiLang( $elm, $attribs ) {
1166  if ( $elm !== self::NS_RDF . ' li' ) {
1167  throw new RuntimeException( __METHOD__ . " <rdf:li> expected but got $elm." );
1168  }
1169  if ( !isset( $attribs[self::NS_XML . ' lang'] )
1170  || !preg_match( '/^[-A-Za-z0-9]{2,}$/D', $attribs[self::NS_XML . ' lang'] )
1171  ) {
1172  throw new RuntimeException( __METHOD__
1173  . " <rdf:li> did not contain, or has invalid xml:lang attribute in lang alternative" );
1174  }
1175 
1176  // Lang is case-insensitive.
1177  $this->itemLang = strtolower( $attribs[self::NS_XML . ' lang'] );
1178 
1179  // need to add curItem[0] on again since one is for the specific item
1180  // and one is for the entire group.
1181  array_unshift( $this->curItem, $this->curItem[0] );
1182  array_unshift( $this->mode, self::MODE_SIMPLE );
1183  $this->processingArray = true;
1184  }
1185 
1196  function startElement( $parser, $elm, $attribs ) {
1197 
1198  if ( $elm === self::NS_RDF . ' RDF'
1199  || $elm === 'adobe:ns:meta/ xmpmeta'
1200  || $elm === 'adobe:ns:meta/ xapmeta'
1201  ) {
1202  /* ignore. */
1203  return;
1204  } elseif ( $elm === self::NS_RDF . ' Description' ) {
1205  if ( count( $this->mode ) === 0 ) {
1206  // outer rdf:desc
1207  array_unshift( $this->mode, self::MODE_INITIAL );
1208  }
1209  } elseif ( $elm === self::NS_RDF . ' type' ) {
1210  // This doesn't support rdf:type properly.
1211  // In practise I have yet to see a file that
1212  // uses this element, however it is mentioned
1213  // on page 25 of part 1 of the xmp standard.
1214  // Also it seems as if exiv2 and exiftool do not support
1215  // this either (That or I misunderstand the standard)
1216  $this->logger->info( __METHOD__ . ' Encountered <rdf:type> which isn\'t currently supported' );
1217  }
1218 
1219  if ( strpos( $elm, ' ' ) === false ) {
1220  // This probably shouldn't happen.
1221  $this->logger->info( __METHOD__ . " Encountered <$elm> which has no namespace. Skipping." );
1222 
1223  return;
1224  }
1225 
1226  list( $ns, $tag ) = explode( ' ', $elm, 2 );
1227 
1228  if ( count( $this->mode ) === 0 ) {
1229  // This should not happen.
1230  throw new RuntimeException( 'Error extracting XMP, '
1231  . "encountered <$elm> with no mode" );
1232  }
1233 
1234  switch ( $this->mode[0] ) {
1235  case self::MODE_IGNORE:
1236  $this->startElementModeIgnore( $elm );
1237  break;
1238  case self::MODE_SIMPLE:
1239  $this->startElementModeSimple( $elm, $attribs );
1240  break;
1241  case self::MODE_INITIAL:
1242  $this->startElementModeInitial( $ns, $tag, $attribs );
1243  break;
1244  case self::MODE_STRUCT:
1245  $this->startElementModeStruct( $ns, $tag, $attribs );
1246  break;
1247  case self::MODE_BAG:
1248  case self::MODE_BAGSTRUCT:
1249  $this->startElementModeBag( $elm );
1250  break;
1251  case self::MODE_SEQ:
1252  $this->startElementModeSeq( $elm );
1253  break;
1254  case self::MODE_LANG:
1255  $this->startElementModeLang( $elm );
1256  break;
1257  case self::MODE_LI_LANG:
1258  $this->startElementModeLiLang( $elm, $attribs );
1259  break;
1260  case self::MODE_LI:
1261  $this->startElementModeLi( $elm, $attribs );
1262  break;
1263  case self::MODE_QDESC:
1264  $this->startElementModeQDesc( $elm );
1265  break;
1266  default:
1267  throw new RuntimeException( 'StartElement in unknown mode: ' . $this->mode[0] );
1268  }
1269  }
1270 
1271  // @codingStandardsIgnoreStart Generic.Files.LineLength
1287  // @codingStandardsIgnoreEnd
1288  private function doAttribs( $attribs ) {
1289  // first check for rdf:parseType attribute, as that can change
1290  // how the attributes are interperted.
1291 
1292  if ( isset( $attribs[self::NS_RDF . ' parseType'] )
1293  && $attribs[self::NS_RDF . ' parseType'] === 'Resource'
1294  && $this->mode[0] === self::MODE_SIMPLE
1295  ) {
1296  // this is equivalent to having an inner rdf:Description
1297  $this->mode[0] = self::MODE_QDESC;
1298  }
1299  foreach ( $attribs as $name => $val ) {
1300  if ( strpos( $name, ' ' ) === false ) {
1301  // This shouldn't happen, but so far some old software forgets namespace
1302  // on rdf:about.
1303  $this->logger->info( __METHOD__ . ' Encountered non-namespaced attribute: '
1304  . " $name=\"$val\". Skipping. " );
1305  continue;
1306  }
1307  list( $ns, $tag ) = explode( ' ', $name, 2 );
1308  if ( $ns === self::NS_RDF ) {
1309  if ( $tag === 'value' || $tag === 'resource' ) {
1310  // resource is for url.
1311  // value attribute is a weird way of just putting the contents.
1312  $this->char( $this->xmlParser, $val );
1313  }
1314  } elseif ( isset( $this->items[$ns][$tag] ) ) {
1315  if ( $this->mode[0] === self::MODE_SIMPLE ) {
1316  throw new RuntimeException( __METHOD__
1317  . " $ns:$tag found as attribute where not allowed" );
1318  }
1319  $this->saveValue( $ns, $tag, $val );
1320  } else {
1321  $this->logger->debug( __METHOD__ . " Ignoring unrecognized element <$ns:$tag>." );
1322  }
1323  }
1324  }
1325 
1337  private function saveValue( $ns, $tag, $val ) {
1338 
1339  $info =& $this->items[$ns][$tag];
1340  $finalName = isset( $info['map_name'] )
1341  ? $info['map_name'] : $tag;
1342  if ( isset( $info['validate'] ) ) {
1343  if ( is_array( $info['validate'] ) ) {
1344  $validate = $info['validate'];
1345  } else {
1346  $validator = new XMPValidate( $this->logger );
1347  $validate = [ $validator, $info['validate'] ];
1348  }
1349 
1350  if ( is_callable( $validate ) ) {
1351  call_user_func_array( $validate, [ $info, &$val, true ] );
1352  // the reasoning behind using &$val instead of using the return value
1353  // is to be consistent between here and validating structures.
1354  if ( is_null( $val ) ) {
1355  $this->logger->info( __METHOD__ . " <$ns:$tag> failed validation." );
1356 
1357  return;
1358  }
1359  } else {
1360  $this->logger->warning( __METHOD__ . " Validation function for $finalName ("
1361  . $validate[0] . '::' . $validate[1] . '()) is not callable.' );
1362  }
1363  }
1364 
1365  if ( $this->ancestorStruct && $this->processingArray ) {
1366  // Aka both an array and a struct. ( self::MODE_BAGSTRUCT )
1367  $this->results['xmp-' . $info['map_group']][$this->ancestorStruct][][$finalName] = $val;
1368  } elseif ( $this->ancestorStruct ) {
1369  $this->results['xmp-' . $info['map_group']][$this->ancestorStruct][$finalName] = $val;
1370  } elseif ( $this->processingArray ) {
1371  if ( $this->itemLang === false ) {
1372  // normal array
1373  $this->results['xmp-' . $info['map_group']][$finalName][] = $val;
1374  } else {
1375  // lang array.
1376  $this->results['xmp-' . $info['map_group']][$finalName][$this->itemLang] = $val;
1377  }
1378  } else {
1379  $this->results['xmp-' . $info['map_group']][$finalName] = $val;
1380  }
1381  }
1382 }
XMPReader\endElement
endElement( $parser, $elm)
Handler for hitting a closing element.
Definition: XMP.php:783
XMPReader\MODE_BAGSTRUCT
const MODE_BAGSTRUCT
Definition: XMP.php:116
XMPReader\$ancestorStruct
bool string $ancestorStruct
The structure name when processing nested structures.
Definition: XMP.php:61
XMPReader\$parsable
int $parsable
Flag determining if the XMP is safe to parse.
Definition: XMP.php:88
XMPReader\startElementModeSimple
startElementModeSimple( $elm, $attribs)
Handle an opening element when in MODE_SIMPLE.
Definition: XMP.php:947
XMPReader\startElementModeInitial
startElementModeInitial( $ns, $tag, $attribs)
Starting an element when in MODE_INITIAL This usually happens when we hit an element inside the outer...
Definition: XMP.php:1005
XMPReader\endElementModeLi
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
XMPReader\$itemLang
bool string $itemLang
Used for lang alts only.
Definition: XMP.php:76
captcha-old.count
count
Definition: captcha-old.py:225
$result
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1954
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
XMPReader\resetXMLParser
resetXMLParser()
Main use is if a single item has multiple xmp documents describing it.
Definition: XMP.php:176
XMPReader\MODE_LI
const MODE_LI
Definition: XMP.php:104
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:304
XMPReader\endElementModeSimple
endElementModeSimple( $elm)
Hit a closing element when in MODE_SIMPLE.
Definition: XMP.php:612
XMPReader\startElement
startElement( $parser, $elm, $attribs)
Hits an opening element.
Definition: XMP.php:1196
php
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
XMPReader\startElementModeLiLang
startElementModeLiLang( $elm, $attribs)
Opening element in MODE_LI_LANG.
Definition: XMP.php:1165
XMPReader\MODE_IGNORE
const MODE_IGNORE
Definition: XMP.php:103
XMPReader\$items
array $items
XMP item configuration array.
Definition: XMP.php:55
XMPReader\MODE_INITIAL
const MODE_INITIAL
These are various mode constants.
Definition: XMP.php:102
XMPReader\endElementModeQDesc
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
XMPReader\MODE_LI_LANG
const MODE_LI_LANG
Definition: XMP.php:105
XMPReader\MODE_SEQ
const MODE_SEQ
Definition: XMP.php:112
XMPReader\MODE_SIMPLE
const MODE_SIMPLE
Definition: XMP.php:110
$content
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content $content
Definition: hooks.txt:1049
XMPReader\endElementModeIgnore
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
$tag
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books $tag
Definition: hooks.txt:1028
mode
if write to the Free Software Franklin Fifth MA USA Also add information on how to contact you by electronic and paper mail If the program is make it output a short notice like this when it starts in an interactive mode
Definition: COPYING.txt:307
$attribs
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:1956
XMPReader\startElementModeSeq
startElementModeSeq( $elm)
Start element in MODE_SEQ (ordered array) this should always be <rdf:Seq>
Definition: XMP.php:894
$parser
do that in ParserLimitReportFormat instead $parser
Definition: hooks.txt:2536
XMPReader\MODE_LANG
const MODE_LANG
Definition: XMP.php:114
XMPReader\startElementModeLang
startElementModeLang( $elm)
Start element in MODE_LANG (language alternative) this should always be <rdf:Alt>
Definition: XMP.php:921
list
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
XMPReader\MODE_BAG
const MODE_BAG
Definition: XMP.php:113
XMPReader\isSupported
static isSupported()
Check if this instance supports using this class.
Definition: XMP.php:197
XMPReader\$charContent
bool string $charContent
Temporary holder for character data that appears in xmp doc.
Definition: XMP.php:64
XMPReader
Class for reading xmp data containing properties relevant to images, and spitting out an array that F...
Definition: XMP.php:53
XMPReader\PARSABLE_BUFFERING
const PARSABLE_BUFFERING
Definition: XMP.php:124
$line
$line
Definition: cdb.php:58
XMPReader\$extendedXMPOffset
int $extendedXMPOffset
Definition: XMP.php:85
XMPReader\$logger
LoggerInterface $logger
Definition: XMP.php:130
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2122
XMPReader\PARSABLE_OK
const PARSABLE_OK
Definition: XMP.php:123
XMPReader\startElementModeStruct
startElementModeStruct( $ns, $tag, $attribs)
Hit an opening element when in a Struct (MODE_STRUCT) This is generally for fields of a compound prop...
Definition: XMP.php:1067
XMPReader\$curItem
array $curItem
Array to hold the current element (and previous element, and so on)
Definition: XMP.php:58
XMPReader\destroyXMLParser
destroyXMLParser()
free the XML parser.
Definition: XMP.php:165
XMPValidate
This contains some static methods for validating XMP properties.
Definition: XMPValidate.php:47
XMPReader\checkParseSafety
checkParseSafety( $content)
Check if a block of XML is safe to pass to xml_parse, i.e.
Definition: XMP.php:535
XMPReader\$charset
bool string $charset
Character set like 'UTF-8'.
Definition: XMP.php:82
XMPReader\startElementModeLi
startElementModeLi( $elm, $attribs)
opening element in MODE_LI process elements of arrays.
Definition: XMP.php:1115
XMPReader\NS_RDF
const NS_RDF
Definition: XMP.php:118
XMPReader\doAttribs
doAttribs( $attribs)
Process attributes.
Definition: XMP.php:1288
$code
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:783
XMPReader\endElementNested
endElementNested( $elm)
Hit a closing element in MODE_STRUCT, MODE_SEQ, MODE_BAG generally means we've finished processing a ...
Definition: XMP.php:647
XMPReader\__construct
__construct(LoggerInterface $logger=null)
Constructor.
Definition: XMP.php:137
XMPInfo\getItems
static getItems()
Get the items array.
Definition: XMPInfo.php:33
XMPReader\MODE_ALT
const MODE_ALT
Definition: XMP.php:115
XMPReader\MODE_STRUCT
const MODE_STRUCT
Definition: XMP.php:111
as
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
XMPReader\$results
array $results
Array to hold results.
Definition: XMP.php:70
XMPReader\startElementModeQDesc
startElementModeQDesc( $elm)
Start an element when in MODE_QDESC.
Definition: XMP.php:983
XMPReader\MODE_QDESC
const MODE_QDESC
Definition: XMP.php:106
XMPReader\NS_XML
const NS_XML
Definition: XMP.php:119
XMPReader\PARSABLE_UNKNOWN
const PARSABLE_UNKNOWN
Definition: XMP.php:122
XMPReader\startElementModeBag
startElementModeBag( $elm)
Start element in MODE_BAG (unordered array) this should always be <rdf:Bag>
Definition: XMP.php:879
XMPReader\parseExtended
parseExtended( $content)
Entry point for XMPExtended blocks in jpeg files.
Definition: XMP.php:418
XMPReader\saveValue
saveValue( $ns, $tag, $val)
Given an extracted value, save it to results array.
Definition: XMP.php:1337
XMPReader\char
char( $parser, $data)
Character data handler Called whenever character data is found in the xmp document.
Definition: XMP.php:498
XMPReader\PARSABLE_NO
const PARSABLE_NO
Definition: XMP.php:125
XMPReader\$xmlParser
resource $xmlParser
A resource handle for the XML parser.
Definition: XMP.php:79
XMPReader\parse
parse( $content, $allOfIt=true)
Main function to call to parse XMP.
Definition: XMP.php:302
XMPReader\$xmlParsableBuffer
string $xmlParsableBuffer
Buffer of XML to parse.
Definition: XMP.php:91
XMPReader\$mode
array $mode
Stores the state the xmpreader is in (see MODE_FOO constants)
Definition: XMP.php:67
XMPReader\setLogger
setLogger(LoggerInterface $logger)
Definition: XMP.php:154
XMPReader\getResults
getResults()
Get the result array.
Definition: XMP.php:207
XMPReader\$processingArray
bool $processingArray
If we're doing a seq or bag.
Definition: XMP.php:73
array
the array() calling protocol came about after MediaWiki 1.4rc1.
XMPReader\startElementModeIgnore
startElementModeIgnore( $elm)
Hit an opening element while in MODE_IGNORE.
Definition: XMP.php:865