MediaWiki  1.30.0
Preprocessor_DOM.php
Go to the documentation of this file.
1 <?php
27 // @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
29  // @codingStandardsIgnoreEnd
30 
34  public $parser;
35 
36  public $memoryLimit;
37 
38  const CACHE_PREFIX = 'preprocess-xml';
39 
40  public function __construct( $parser ) {
41  $this->parser = $parser;
42  $mem = ini_get( 'memory_limit' );
43  $this->memoryLimit = false;
44  if ( strval( $mem ) !== '' && $mem != -1 ) {
45  if ( preg_match( '/^\d+$/', $mem ) ) {
46  $this->memoryLimit = $mem;
47  } elseif ( preg_match( '/^(\d+)M$/i', $mem, $m ) ) {
48  $this->memoryLimit = $m[1] * 1048576;
49  }
50  }
51  }
52 
56  public function newFrame() {
57  return new PPFrame_DOM( $this );
58  }
59 
64  public function newCustomFrame( $args ) {
65  return new PPCustomFrame_DOM( $this, $args );
66  }
67 
73  public function newPartNodeArray( $values ) {
74  // NOTE: DOM manipulation is slower than building & parsing XML! (or so Tim sais)
75  $xml = "<list>";
76 
77  foreach ( $values as $k => $val ) {
78  if ( is_int( $k ) ) {
79  $xml .= "<part><name index=\"$k\"/><value>"
80  . htmlspecialchars( $val ) . "</value></part>";
81  } else {
82  $xml .= "<part><name>" . htmlspecialchars( $k )
83  . "</name>=<value>" . htmlspecialchars( $val ) . "</value></part>";
84  }
85  }
86 
87  $xml .= "</list>";
88 
89  $dom = new DOMDocument();
90  MediaWiki\suppressWarnings();
91  $result = $dom->loadXML( $xml );
92  MediaWiki\restoreWarnings();
93  if ( !$result ) {
94  // Try running the XML through UtfNormal to get rid of invalid characters
95  $xml = UtfNormal\Validator::cleanUp( $xml );
96  // 1 << 19 == XML_PARSE_HUGE, needed so newer versions of libxml2
97  // don't barf when the XML is >256 levels deep
98  $result = $dom->loadXML( $xml, 1 << 19 );
99  }
100 
101  if ( !$result ) {
102  throw new MWException( 'Parameters passed to ' . __METHOD__ . ' result in invalid XML' );
103  }
104 
105  $root = $dom->documentElement;
106  $node = new PPNode_DOM( $root->childNodes );
107  return $node;
108  }
109 
114  public function memCheck() {
115  if ( $this->memoryLimit === false ) {
116  return true;
117  }
118  $usage = memory_get_usage();
119  if ( $usage > $this->memoryLimit * 0.9 ) {
120  $limit = intval( $this->memoryLimit * 0.9 / 1048576 + 0.5 );
121  throw new MWException( "Preprocessor hit 90% memory limit ($limit MB)" );
122  }
123  return $usage <= $this->memoryLimit * 0.8;
124  }
125 
150  public function preprocessToObj( $text, $flags = 0 ) {
151  $xml = $this->cacheGetTree( $text, $flags );
152  if ( $xml === false ) {
153  $xml = $this->preprocessToXml( $text, $flags );
154  $this->cacheSetTree( $text, $flags, $xml );
155  }
156 
157  // Fail if the number of elements exceeds acceptable limits
158  // Do not attempt to generate the DOM
159  $this->parser->mGeneratedPPNodeCount += substr_count( $xml, '<' );
160  $max = $this->parser->mOptions->getMaxGeneratedPPNodeCount();
161  if ( $this->parser->mGeneratedPPNodeCount > $max ) {
162  // if ( $cacheable ) { ... }
163  throw new MWException( __METHOD__ . ': generated node count limit exceeded' );
164  }
165 
166  $dom = new DOMDocument;
167  MediaWiki\suppressWarnings();
168  $result = $dom->loadXML( $xml );
169  MediaWiki\restoreWarnings();
170  if ( !$result ) {
171  // Try running the XML through UtfNormal to get rid of invalid characters
172  $xml = UtfNormal\Validator::cleanUp( $xml );
173  // 1 << 19 == XML_PARSE_HUGE, needed so newer versions of libxml2
174  // don't barf when the XML is >256 levels deep.
175  $result = $dom->loadXML( $xml, 1 << 19 );
176  }
177  if ( $result ) {
178  $obj = new PPNode_DOM( $dom->documentElement );
179  }
180 
181  // if ( $cacheable ) { ... }
182 
183  if ( !$result ) {
184  throw new MWException( __METHOD__ . ' generated invalid XML' );
185  }
186  return $obj;
187  }
188 
194  public function preprocessToXml( $text, $flags = 0 ) {
196 
197  $forInclusion = $flags & Parser::PTD_FOR_INCLUSION;
198 
199  $xmlishElements = $this->parser->getStripList();
200  $xmlishAllowMissingEndTag = [ 'includeonly', 'noinclude', 'onlyinclude' ];
201  $enableOnlyinclude = false;
202  if ( $forInclusion ) {
203  $ignoredTags = [ 'includeonly', '/includeonly' ];
204  $ignoredElements = [ 'noinclude' ];
205  $xmlishElements[] = 'noinclude';
206  if ( strpos( $text, '<onlyinclude>' ) !== false
207  && strpos( $text, '</onlyinclude>' ) !== false
208  ) {
209  $enableOnlyinclude = true;
210  }
211  } else {
212  $ignoredTags = [ 'noinclude', '/noinclude', 'onlyinclude', '/onlyinclude' ];
213  $ignoredElements = [ 'includeonly' ];
214  $xmlishElements[] = 'includeonly';
215  }
216  $xmlishRegex = implode( '|', array_merge( $xmlishElements, $ignoredTags ) );
217 
218  // Use "A" modifier (anchored) instead of "^", because ^ doesn't work with an offset
219  $elementsRegex = "~($xmlishRegex)(?:\s|\/>|>)|(!--)~iA";
220 
221  $stack = new PPDStack;
222 
223  $searchBase = "[{<\n"; # }
224  if ( !$wgDisableLangConversion ) {
225  $searchBase .= '-';
226  }
227 
228  // For fast reverse searches
229  $revText = strrev( $text );
230  $lengthText = strlen( $text );
231 
232  // Input pointer, starts out pointing to a pseudo-newline before the start
233  $i = 0;
234  // Current accumulator
235  $accum =& $stack->getAccum();
236  $accum = '<root>';
237  // True to find equals signs in arguments
238  $findEquals = false;
239  // True to take notice of pipe characters
240  $findPipe = false;
241  $headingIndex = 1;
242  // True if $i is inside a possible heading
243  $inHeading = false;
244  // True if there are no more greater-than (>) signs right of $i
245  $noMoreGT = false;
246  // Map of tag name => true if there are no more closing tags of given type right of $i
247  $noMoreClosingTag = [];
248  // True to ignore all input up to the next <onlyinclude>
249  $findOnlyinclude = $enableOnlyinclude;
250  // Do a line-start run without outputting an LF character
251  $fakeLineStart = true;
252 
253  while ( true ) {
254  // $this->memCheck();
255 
256  if ( $findOnlyinclude ) {
257  // Ignore all input up to the next <onlyinclude>
258  $startPos = strpos( $text, '<onlyinclude>', $i );
259  if ( $startPos === false ) {
260  // Ignored section runs to the end
261  $accum .= '<ignore>' . htmlspecialchars( substr( $text, $i ) ) . '</ignore>';
262  break;
263  }
264  $tagEndPos = $startPos + strlen( '<onlyinclude>' ); // past-the-end
265  $accum .= '<ignore>' . htmlspecialchars( substr( $text, $i, $tagEndPos - $i ) ) . '</ignore>';
266  $i = $tagEndPos;
267  $findOnlyinclude = false;
268  }
269 
270  if ( $fakeLineStart ) {
271  $found = 'line-start';
272  $curChar = '';
273  } else {
274  # Find next opening brace, closing brace or pipe
275  $search = $searchBase;
276  if ( $stack->top === false ) {
277  $currentClosing = '';
278  } elseif (
279  $stack->top->close === '}-' &&
280  $stack->top->count > 2
281  ) {
282  # adjust closing for -{{{...{{
283  $currentClosing = '}';
284  $search .= $currentClosing;
285  } else {
286  $currentClosing = $stack->top->close;
287  $search .= $currentClosing;
288  }
289  if ( $findPipe ) {
290  $search .= '|';
291  }
292  if ( $findEquals ) {
293  // First equals will be for the template
294  $search .= '=';
295  }
296  $rule = null;
297  # Output literal section, advance input counter
298  $literalLength = strcspn( $text, $search, $i );
299  if ( $literalLength > 0 ) {
300  $accum .= htmlspecialchars( substr( $text, $i, $literalLength ) );
301  $i += $literalLength;
302  }
303  if ( $i >= $lengthText ) {
304  if ( $currentClosing == "\n" ) {
305  // Do a past-the-end run to finish off the heading
306  $curChar = '';
307  $found = 'line-end';
308  } else {
309  # All done
310  break;
311  }
312  } else {
313  $curChar = $curTwoChar = $text[$i];
314  if ( ( $i + 1 ) < $lengthText ) {
315  $curTwoChar .= $text[$i + 1];
316  }
317  if ( $curChar == '|' ) {
318  $found = 'pipe';
319  } elseif ( $curChar == '=' ) {
320  $found = 'equals';
321  } elseif ( $curChar == '<' ) {
322  $found = 'angle';
323  } elseif ( $curChar == "\n" ) {
324  if ( $inHeading ) {
325  $found = 'line-end';
326  } else {
327  $found = 'line-start';
328  }
329  } elseif ( $curTwoChar == $currentClosing ) {
330  $found = 'close';
331  $curChar = $curTwoChar;
332  } elseif ( $curChar == $currentClosing ) {
333  $found = 'close';
334  } elseif ( isset( $this->rules[$curTwoChar] ) ) {
335  $curChar = $curTwoChar;
336  $found = 'open';
337  $rule = $this->rules[$curChar];
338  } elseif ( isset( $this->rules[$curChar] ) ) {
339  $found = 'open';
340  $rule = $this->rules[$curChar];
341  } else {
342  # Some versions of PHP have a strcspn which stops on
343  # null characters; ignore these and continue.
344  # We also may get '-' and '}' characters here which
345  # don't match -{ or $currentClosing. Add these to
346  # output and continue.
347  if ( $curChar == '-' || $curChar == '}' ) {
348  $accum .= $curChar;
349  }
350  ++$i;
351  continue;
352  }
353  }
354  }
355 
356  if ( $found == 'angle' ) {
357  $matches = false;
358  // Handle </onlyinclude>
359  if ( $enableOnlyinclude
360  && substr( $text, $i, strlen( '</onlyinclude>' ) ) == '</onlyinclude>'
361  ) {
362  $findOnlyinclude = true;
363  continue;
364  }
365 
366  // Determine element name
367  if ( !preg_match( $elementsRegex, $text, $matches, 0, $i + 1 ) ) {
368  // Element name missing or not listed
369  $accum .= '&lt;';
370  ++$i;
371  continue;
372  }
373  // Handle comments
374  if ( isset( $matches[2] ) && $matches[2] == '!--' ) {
375  // To avoid leaving blank lines, when a sequence of
376  // space-separated comments is both preceded and followed by
377  // a newline (ignoring spaces), then
378  // trim leading and trailing spaces and the trailing newline.
379 
380  // Find the end
381  $endPos = strpos( $text, '-->', $i + 4 );
382  if ( $endPos === false ) {
383  // Unclosed comment in input, runs to end
384  $inner = substr( $text, $i );
385  $accum .= '<comment>' . htmlspecialchars( $inner ) . '</comment>';
386  $i = $lengthText;
387  } else {
388  // Search backwards for leading whitespace
389  $wsStart = $i ? ( $i - strspn( $revText, " \t", $lengthText - $i ) ) : 0;
390 
391  // Search forwards for trailing whitespace
392  // $wsEnd will be the position of the last space (or the '>' if there's none)
393  $wsEnd = $endPos + 2 + strspn( $text, " \t", $endPos + 3 );
394 
395  // Keep looking forward as long as we're finding more
396  // comments.
397  $comments = [ [ $wsStart, $wsEnd ] ];
398  while ( substr( $text, $wsEnd + 1, 4 ) == '<!--' ) {
399  $c = strpos( $text, '-->', $wsEnd + 4 );
400  if ( $c === false ) {
401  break;
402  }
403  $c = $c + 2 + strspn( $text, " \t", $c + 3 );
404  $comments[] = [ $wsEnd + 1, $c ];
405  $wsEnd = $c;
406  }
407 
408  // Eat the line if possible
409  // TODO: This could theoretically be done if $wsStart == 0, i.e. for comments at
410  // the overall start. That's not how Sanitizer::removeHTMLcomments() did it, but
411  // it's a possible beneficial b/c break.
412  if ( $wsStart > 0 && substr( $text, $wsStart - 1, 1 ) == "\n"
413  && substr( $text, $wsEnd + 1, 1 ) == "\n"
414  ) {
415  // Remove leading whitespace from the end of the accumulator
416  // Sanity check first though
417  $wsLength = $i - $wsStart;
418  if ( $wsLength > 0
419  && strspn( $accum, " \t", -$wsLength ) === $wsLength
420  ) {
421  $accum = substr( $accum, 0, -$wsLength );
422  }
423 
424  // Dump all but the last comment to the accumulator
425  foreach ( $comments as $j => $com ) {
426  $startPos = $com[0];
427  $endPos = $com[1] + 1;
428  if ( $j == ( count( $comments ) - 1 ) ) {
429  break;
430  }
431  $inner = substr( $text, $startPos, $endPos - $startPos );
432  $accum .= '<comment>' . htmlspecialchars( $inner ) . '</comment>';
433  }
434 
435  // Do a line-start run next time to look for headings after the comment
436  $fakeLineStart = true;
437  } else {
438  // No line to eat, just take the comment itself
439  $startPos = $i;
440  $endPos += 2;
441  }
442 
443  if ( $stack->top ) {
444  $part = $stack->top->getCurrentPart();
445  if ( !( isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 ) ) {
446  $part->visualEnd = $wsStart;
447  }
448  // Else comments abutting, no change in visual end
449  $part->commentEnd = $endPos;
450  }
451  $i = $endPos + 1;
452  $inner = substr( $text, $startPos, $endPos - $startPos + 1 );
453  $accum .= '<comment>' . htmlspecialchars( $inner ) . '</comment>';
454  }
455  continue;
456  }
457  $name = $matches[1];
458  $lowerName = strtolower( $name );
459  $attrStart = $i + strlen( $name ) + 1;
460 
461  // Find end of tag
462  $tagEndPos = $noMoreGT ? false : strpos( $text, '>', $attrStart );
463  if ( $tagEndPos === false ) {
464  // Infinite backtrack
465  // Disable tag search to prevent worst-case O(N^2) performance
466  $noMoreGT = true;
467  $accum .= '&lt;';
468  ++$i;
469  continue;
470  }
471 
472  // Handle ignored tags
473  if ( in_array( $lowerName, $ignoredTags ) ) {
474  $accum .= '<ignore>'
475  . htmlspecialchars( substr( $text, $i, $tagEndPos - $i + 1 ) )
476  . '</ignore>';
477  $i = $tagEndPos + 1;
478  continue;
479  }
480 
481  $tagStartPos = $i;
482  if ( $text[$tagEndPos - 1] == '/' ) {
483  $attrEnd = $tagEndPos - 1;
484  $inner = null;
485  $i = $tagEndPos + 1;
486  $close = '';
487  } else {
488  $attrEnd = $tagEndPos;
489  // Find closing tag
490  if (
491  !isset( $noMoreClosingTag[$name] ) &&
492  preg_match( "/<\/" . preg_quote( $name, '/' ) . "\s*>/i",
493  $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 )
494  ) {
495  $inner = substr( $text, $tagEndPos + 1, $matches[0][1] - $tagEndPos - 1 );
496  $i = $matches[0][1] + strlen( $matches[0][0] );
497  $close = '<close>' . htmlspecialchars( $matches[0][0] ) . '</close>';
498  } else {
499  // No end tag
500  if ( in_array( $name, $xmlishAllowMissingEndTag ) ) {
501  // Let it run out to the end of the text.
502  $inner = substr( $text, $tagEndPos + 1 );
503  $i = $lengthText;
504  $close = '';
505  } else {
506  // Don't match the tag, treat opening tag as literal and resume parsing.
507  $i = $tagEndPos + 1;
508  $accum .= htmlspecialchars( substr( $text, $tagStartPos, $tagEndPos + 1 - $tagStartPos ) );
509  // Cache results, otherwise we have O(N^2) performance for input like <foo><foo><foo>...
510  $noMoreClosingTag[$name] = true;
511  continue;
512  }
513  }
514  }
515  // <includeonly> and <noinclude> just become <ignore> tags
516  if ( in_array( $lowerName, $ignoredElements ) ) {
517  $accum .= '<ignore>' . htmlspecialchars( substr( $text, $tagStartPos, $i - $tagStartPos ) )
518  . '</ignore>';
519  continue;
520  }
521 
522  $accum .= '<ext>';
523  if ( $attrEnd <= $attrStart ) {
524  $attr = '';
525  } else {
526  $attr = substr( $text, $attrStart, $attrEnd - $attrStart );
527  }
528  $accum .= '<name>' . htmlspecialchars( $name ) . '</name>' .
529  // Note that the attr element contains the whitespace between name and attribute,
530  // this is necessary for precise reconstruction during pre-save transform.
531  '<attr>' . htmlspecialchars( $attr ) . '</attr>';
532  if ( $inner !== null ) {
533  $accum .= '<inner>' . htmlspecialchars( $inner ) . '</inner>';
534  }
535  $accum .= $close . '</ext>';
536  } elseif ( $found == 'line-start' ) {
537  // Is this the start of a heading?
538  // Line break belongs before the heading element in any case
539  if ( $fakeLineStart ) {
540  $fakeLineStart = false;
541  } else {
542  $accum .= $curChar;
543  $i++;
544  }
545 
546  $count = strspn( $text, '=', $i, 6 );
547  if ( $count == 1 && $findEquals ) {
548  // DWIM: This looks kind of like a name/value separator.
549  // Let's let the equals handler have it and break the
550  // potential heading. This is heuristic, but AFAICT the
551  // methods for completely correct disambiguation are very
552  // complex.
553  } elseif ( $count > 0 ) {
554  $piece = [
555  'open' => "\n",
556  'close' => "\n",
557  'parts' => [ new PPDPart( str_repeat( '=', $count ) ) ],
558  'startPos' => $i,
559  'count' => $count ];
560  $stack->push( $piece );
561  $accum =& $stack->getAccum();
562  $flags = $stack->getFlags();
563  extract( $flags );
564  $i += $count;
565  }
566  } elseif ( $found == 'line-end' ) {
567  $piece = $stack->top;
568  // A heading must be open, otherwise \n wouldn't have been in the search list
569  assert( $piece->open === "\n" );
570  $part = $piece->getCurrentPart();
571  // Search back through the input to see if it has a proper close.
572  // Do this using the reversed string since the other solutions
573  // (end anchor, etc.) are inefficient.
574  $wsLength = strspn( $revText, " \t", $lengthText - $i );
575  $searchStart = $i - $wsLength;
576  if ( isset( $part->commentEnd ) && $searchStart - 1 == $part->commentEnd ) {
577  // Comment found at line end
578  // Search for equals signs before the comment
579  $searchStart = $part->visualEnd;
580  $searchStart -= strspn( $revText, " \t", $lengthText - $searchStart );
581  }
582  $count = $piece->count;
583  $equalsLength = strspn( $revText, '=', $lengthText - $searchStart );
584  if ( $equalsLength > 0 ) {
585  if ( $searchStart - $equalsLength == $piece->startPos ) {
586  // This is just a single string of equals signs on its own line
587  // Replicate the doHeadings behavior /={count}(.+)={count}/
588  // First find out how many equals signs there really are (don't stop at 6)
589  $count = $equalsLength;
590  if ( $count < 3 ) {
591  $count = 0;
592  } else {
593  $count = min( 6, intval( ( $count - 1 ) / 2 ) );
594  }
595  } else {
596  $count = min( $equalsLength, $count );
597  }
598  if ( $count > 0 ) {
599  // Normal match, output <h>
600  $element = "<h level=\"$count\" i=\"$headingIndex\">$accum</h>";
601  $headingIndex++;
602  } else {
603  // Single equals sign on its own line, count=0
604  $element = $accum;
605  }
606  } else {
607  // No match, no <h>, just pass down the inner text
608  $element = $accum;
609  }
610  // Unwind the stack
611  $stack->pop();
612  $accum =& $stack->getAccum();
613  $flags = $stack->getFlags();
614  extract( $flags );
615 
616  // Append the result to the enclosing accumulator
617  $accum .= $element;
618  // Note that we do NOT increment the input pointer.
619  // This is because the closing linebreak could be the opening linebreak of
620  // another heading. Infinite loops are avoided because the next iteration MUST
621  // hit the heading open case above, which unconditionally increments the
622  // input pointer.
623  } elseif ( $found == 'open' ) {
624  # count opening brace characters
625  $curLen = strlen( $curChar );
626  $count = ( $curLen > 1 ) ?
627  # allow the final character to repeat
628  strspn( $text, $curChar[$curLen - 1], $i + 1 ) + 1 :
629  strspn( $text, $curChar, $i );
630 
631  # we need to add to stack only if opening brace count is enough for one of the rules
632  if ( $count >= $rule['min'] ) {
633  # Add it to the stack
634  $piece = [
635  'open' => $curChar,
636  'close' => $rule['end'],
637  'count' => $count,
638  'lineStart' => ( $i > 0 && $text[$i - 1] == "\n" ),
639  ];
640 
641  $stack->push( $piece );
642  $accum =& $stack->getAccum();
643  $flags = $stack->getFlags();
644  extract( $flags );
645  } else {
646  # Add literal brace(s)
647  $accum .= htmlspecialchars( str_repeat( $curChar, $count ) );
648  }
649  $i += $count;
650  } elseif ( $found == 'close' ) {
651  $piece = $stack->top;
652  # lets check if there are enough characters for closing brace
653  $maxCount = $piece->count;
654  if ( $piece->close === '}-' && $curChar === '}' ) {
655  $maxCount--; # don't try to match closing '-' as a '}'
656  }
657  $curLen = strlen( $curChar );
658  $count = ( $curLen > 1 ) ? $curLen :
659  strspn( $text, $curChar, $i, $maxCount );
660 
661  # check for maximum matching characters (if there are 5 closing
662  # characters, we will probably need only 3 - depending on the rules)
663  $rule = $this->rules[$piece->open];
664  if ( $piece->close === '}-' && $piece->count > 2 ) {
665  # tweak for -{..{{ }}..}-
666  $rule = $this->rules['{'];
667  }
668  if ( $count > $rule['max'] ) {
669  # The specified maximum exists in the callback array, unless the caller
670  # has made an error
671  $matchingCount = $rule['max'];
672  } else {
673  # Count is less than the maximum
674  # Skip any gaps in the callback array to find the true largest match
675  # Need to use array_key_exists not isset because the callback can be null
676  $matchingCount = $count;
677  while ( $matchingCount > 0 && !array_key_exists( $matchingCount, $rule['names'] ) ) {
678  --$matchingCount;
679  }
680  }
681 
682  if ( $matchingCount <= 0 ) {
683  # No matching element found in callback array
684  # Output a literal closing brace and continue
685  $endText = substr( $text, $i, $count );
686  $accum .= htmlspecialchars( $endText );
687  $i += $count;
688  continue;
689  }
690  $name = $rule['names'][$matchingCount];
691  if ( $name === null ) {
692  // No element, just literal text
693  $endText = substr( $text, $i, $matchingCount );
694  $element = $piece->breakSyntax( $matchingCount ) . $endText;
695  } else {
696  # Create XML element
697  # Note: $parts is already XML, does not need to be encoded further
698  $parts = $piece->parts;
699  $title = $parts[0]->out;
700  unset( $parts[0] );
701 
702  # The invocation is at the start of the line if lineStart is set in
703  # the stack, and all opening brackets are used up.
704  if ( $maxCount == $matchingCount && !empty( $piece->lineStart ) ) {
705  $attr = ' lineStart="1"';
706  } else {
707  $attr = '';
708  }
709 
710  $element = "<$name$attr>";
711  $element .= "<title>$title</title>";
712  $argIndex = 1;
713  foreach ( $parts as $part ) {
714  if ( isset( $part->eqpos ) ) {
715  $argName = substr( $part->out, 0, $part->eqpos );
716  $argValue = substr( $part->out, $part->eqpos + 1 );
717  $element .= "<part><name>$argName</name>=<value>$argValue</value></part>";
718  } else {
719  $element .= "<part><name index=\"$argIndex\" /><value>{$part->out}</value></part>";
720  $argIndex++;
721  }
722  }
723  $element .= "</$name>";
724  }
725 
726  # Advance input pointer
727  $i += $matchingCount;
728 
729  # Unwind the stack
730  $stack->pop();
731  $accum =& $stack->getAccum();
732 
733  # Re-add the old stack element if it still has unmatched opening characters remaining
734  if ( $matchingCount < $piece->count ) {
735  $piece->parts = [ new PPDPart ];
736  $piece->count -= $matchingCount;
737  # do we still qualify for any callback with remaining count?
738  $min = $this->rules[$piece->open]['min'];
739  if ( $piece->count >= $min ) {
740  $stack->push( $piece );
741  $accum =& $stack->getAccum();
742  } else {
743  $s = substr( $piece->open, 0, -1 );
744  $s .= str_repeat(
745  substr( $piece->open, -1 ),
746  $piece->count - strlen( $s )
747  );
748  $accum .= $s;
749  }
750  }
751  $flags = $stack->getFlags();
752  extract( $flags );
753 
754  # Add XML element to the enclosing accumulator
755  $accum .= $element;
756  } elseif ( $found == 'pipe' ) {
757  $findEquals = true; // shortcut for getFlags()
758  $stack->addPart();
759  $accum =& $stack->getAccum();
760  ++$i;
761  } elseif ( $found == 'equals' ) {
762  $findEquals = false; // shortcut for getFlags()
763  $stack->getCurrentPart()->eqpos = strlen( $accum );
764  $accum .= '=';
765  ++$i;
766  } elseif ( $found == 'dash' ) {
767  $accum .= '-';
768  ++$i;
769  }
770  }
771 
772  # Output any remaining unclosed brackets
773  foreach ( $stack->stack as $piece ) {
774  $stack->rootAccum .= $piece->breakSyntax();
775  }
776  $stack->rootAccum .= '</root>';
777  $xml = $stack->rootAccum;
778 
779  return $xml;
780  }
781 }
782 
787 class PPDStack {
788  public $stack, $rootAccum;
789 
793  public $top;
794  public $out;
795  public $elementClass = 'PPDStackElement';
796 
797  public static $false = false;
798 
799  public function __construct() {
800  $this->stack = [];
801  $this->top = false;
802  $this->rootAccum = '';
803  $this->accum =& $this->rootAccum;
804  }
805 
809  public function count() {
810  return count( $this->stack );
811  }
812 
813  public function &getAccum() {
814  return $this->accum;
815  }
816 
817  public function getCurrentPart() {
818  if ( $this->top === false ) {
819  return false;
820  } else {
821  return $this->top->getCurrentPart();
822  }
823  }
824 
825  public function push( $data ) {
826  if ( $data instanceof $this->elementClass ) {
827  $this->stack[] = $data;
828  } else {
829  $class = $this->elementClass;
830  $this->stack[] = new $class( $data );
831  }
832  $this->top = $this->stack[count( $this->stack ) - 1];
833  $this->accum =& $this->top->getAccum();
834  }
835 
836  public function pop() {
837  if ( !count( $this->stack ) ) {
838  throw new MWException( __METHOD__ . ': no elements remaining' );
839  }
840  $temp = array_pop( $this->stack );
841 
842  if ( count( $this->stack ) ) {
843  $this->top = $this->stack[count( $this->stack ) - 1];
844  $this->accum =& $this->top->getAccum();
845  } else {
846  $this->top = self::$false;
847  $this->accum =& $this->rootAccum;
848  }
849  return $temp;
850  }
851 
852  public function addPart( $s = '' ) {
853  $this->top->addPart( $s );
854  $this->accum =& $this->top->getAccum();
855  }
856 
860  public function getFlags() {
861  if ( !count( $this->stack ) ) {
862  return [
863  'findEquals' => false,
864  'findPipe' => false,
865  'inHeading' => false,
866  ];
867  } else {
868  return $this->top->getFlags();
869  }
870  }
871 }
872 
876 class PPDStackElement {
880  public $open;
881 
885  public $close;
886 
890  public $count;
891 
895  public $parts;
896 
901  public $lineStart;
902 
903  public $partClass = 'PPDPart';
904 
905  public function __construct( $data = [] ) {
906  $class = $this->partClass;
907  $this->parts = [ new $class ];
908 
909  foreach ( $data as $name => $value ) {
910  $this->$name = $value;
911  }
912  }
913 
914  public function &getAccum() {
915  return $this->parts[count( $this->parts ) - 1]->out;
916  }
917 
918  public function addPart( $s = '' ) {
919  $class = $this->partClass;
920  $this->parts[] = new $class( $s );
921  }
922 
923  public function getCurrentPart() {
924  return $this->parts[count( $this->parts ) - 1];
925  }
926 
930  public function getFlags() {
931  $partCount = count( $this->parts );
932  $findPipe = $this->open != "\n" && $this->open != '[';
933  return [
934  'findPipe' => $findPipe,
935  'findEquals' => $findPipe && $partCount > 1 && !isset( $this->parts[$partCount - 1]->eqpos ),
936  'inHeading' => $this->open == "\n",
937  ];
938  }
939 
946  public function breakSyntax( $openingCount = false ) {
947  if ( $this->open == "\n" ) {
948  $s = $this->parts[0]->out;
949  } else {
950  if ( $openingCount === false ) {
951  $openingCount = $this->count;
952  }
953  $s = substr( $this->open, 0, -1 );
954  $s .= str_repeat(
955  substr( $this->open, -1 ),
956  $openingCount - strlen( $s )
957  );
958  $first = true;
959  foreach ( $this->parts as $part ) {
960  if ( $first ) {
961  $first = false;
962  } else {
963  $s .= '|';
964  }
965  $s .= $part->out;
966  }
967  }
968  return $s;
969  }
970 }
971 
975 class PPDPart {
979  public $out;
980 
981  // Optional member variables:
982  // eqpos Position of equals sign in output accumulator
983  // commentEnd Past-the-end input pointer for the last comment encountered
984  // visualEnd Past-the-end input pointer for the end of the accumulator minus comments
985 
986  public function __construct( $out = '' ) {
987  $this->out = $out;
988  }
989 }
990 
995 // @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
996 class PPFrame_DOM implements PPFrame {
997  // @codingStandardsIgnoreEnd
998 
1002  public $preprocessor;
1003 
1007  public $parser;
1008 
1012  public $title;
1013  public $titleCache;
1014 
1019  public $loopCheckHash;
1020 
1025  public $depth;
1026 
1027  private $volatile = false;
1028  private $ttl = null;
1029 
1033  protected $childExpansionCache;
1034 
1039  public function __construct( $preprocessor ) {
1040  $this->preprocessor = $preprocessor;
1041  $this->parser = $preprocessor->parser;
1042  $this->title = $this->parser->mTitle;
1043  $this->titleCache = [ $this->title ? $this->title->getPrefixedDBkey() : false ];
1044  $this->loopCheckHash = [];
1045  $this->depth = 0;
1046  $this->childExpansionCache = [];
1047  }
1048 
1058  public function newChild( $args = false, $title = false, $indexOffset = 0 ) {
1059  $namedArgs = [];
1060  $numberedArgs = [];
1061  if ( $title === false ) {
1062  $title = $this->title;
1063  }
1064  if ( $args !== false ) {
1065  $xpath = false;
1066  if ( $args instanceof PPNode ) {
1067  $args = $args->node;
1068  }
1069  foreach ( $args as $arg ) {
1070  if ( $arg instanceof PPNode ) {
1071  $arg = $arg->node;
1072  }
1073  if ( !$xpath || $xpath->document !== $arg->ownerDocument ) {
1074  $xpath = new DOMXPath( $arg->ownerDocument );
1075  }
1076 
1077  $nameNodes = $xpath->query( 'name', $arg );
1078  $value = $xpath->query( 'value', $arg );
1079  if ( $nameNodes->item( 0 )->hasAttributes() ) {
1080  // Numbered parameter
1081  $index = $nameNodes->item( 0 )->attributes->getNamedItem( 'index' )->textContent;
1082  $index = $index - $indexOffset;
1083  if ( isset( $namedArgs[$index] ) || isset( $numberedArgs[$index] ) ) {
1084  $this->parser->getOutput()->addWarning( wfMessage( 'duplicate-args-warning',
1085  wfEscapeWikiText( $this->title ),
1086  wfEscapeWikiText( $title ),
1087  wfEscapeWikiText( $index ) )->text() );
1088  $this->parser->addTrackingCategory( 'duplicate-args-category' );
1089  }
1090  $numberedArgs[$index] = $value->item( 0 );
1091  unset( $namedArgs[$index] );
1092  } else {
1093  // Named parameter
1094  $name = trim( $this->expand( $nameNodes->item( 0 ), PPFrame::STRIP_COMMENTS ) );
1095  if ( isset( $namedArgs[$name] ) || isset( $numberedArgs[$name] ) ) {
1096  $this->parser->getOutput()->addWarning( wfMessage( 'duplicate-args-warning',
1097  wfEscapeWikiText( $this->title ),
1098  wfEscapeWikiText( $title ),
1099  wfEscapeWikiText( $name ) )->text() );
1100  $this->parser->addTrackingCategory( 'duplicate-args-category' );
1101  }
1102  $namedArgs[$name] = $value->item( 0 );
1103  unset( $numberedArgs[$name] );
1104  }
1105  }
1106  }
1107  return new PPTemplateFrame_DOM( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title );
1108  }
1109 
1117  public function cachedExpand( $key, $root, $flags = 0 ) {
1118  // we don't have a parent, so we don't have a cache
1119  return $this->expand( $root, $flags );
1120  }
1121 
1128  public function expand( $root, $flags = 0 ) {
1129  static $expansionDepth = 0;
1130  if ( is_string( $root ) ) {
1131  return $root;
1132  }
1133 
1134  if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount() ) {
1135  $this->parser->limitationWarn( 'node-count-exceeded',
1136  $this->parser->mPPNodeCount,
1137  $this->parser->mOptions->getMaxPPNodeCount()
1138  );
1139  return '<span class="error">Node-count limit exceeded</span>';
1140  }
1141 
1142  if ( $expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth() ) {
1143  $this->parser->limitationWarn( 'expansion-depth-exceeded',
1144  $expansionDepth,
1145  $this->parser->mOptions->getMaxPPExpandDepth()
1146  );
1147  return '<span class="error">Expansion depth limit exceeded</span>';
1148  }
1149  ++$expansionDepth;
1150  if ( $expansionDepth > $this->parser->mHighestExpansionDepth ) {
1151  $this->parser->mHighestExpansionDepth = $expansionDepth;
1152  }
1153 
1154  if ( $root instanceof PPNode_DOM ) {
1155  $root = $root->node;
1156  }
1157  if ( $root instanceof DOMDocument ) {
1158  $root = $root->documentElement;
1159  }
1160 
1161  $outStack = [ '', '' ];
1162  $iteratorStack = [ false, $root ];
1163  $indexStack = [ 0, 0 ];
1164 
1165  while ( count( $iteratorStack ) > 1 ) {
1166  $level = count( $outStack ) - 1;
1167  $iteratorNode =& $iteratorStack[$level];
1168  $out =& $outStack[$level];
1169  $index =& $indexStack[$level];
1170 
1171  if ( $iteratorNode instanceof PPNode_DOM ) {
1172  $iteratorNode = $iteratorNode->node;
1173  }
1174 
1175  if ( is_array( $iteratorNode ) ) {
1176  if ( $index >= count( $iteratorNode ) ) {
1177  // All done with this iterator
1178  $iteratorStack[$level] = false;
1179  $contextNode = false;
1180  } else {
1181  $contextNode = $iteratorNode[$index];
1182  $index++;
1183  }
1184  } elseif ( $iteratorNode instanceof DOMNodeList ) {
1185  if ( $index >= $iteratorNode->length ) {
1186  // All done with this iterator
1187  $iteratorStack[$level] = false;
1188  $contextNode = false;
1189  } else {
1190  $contextNode = $iteratorNode->item( $index );
1191  $index++;
1192  }
1193  } else {
1194  // Copy to $contextNode and then delete from iterator stack,
1195  // because this is not an iterator but we do have to execute it once
1196  $contextNode = $iteratorStack[$level];
1197  $iteratorStack[$level] = false;
1198  }
1199 
1200  if ( $contextNode instanceof PPNode_DOM ) {
1201  $contextNode = $contextNode->node;
1202  }
1203 
1204  $newIterator = false;
1205 
1206  if ( $contextNode === false ) {
1207  // nothing to do
1208  } elseif ( is_string( $contextNode ) ) {
1209  $out .= $contextNode;
1210  } elseif ( is_array( $contextNode ) || $contextNode instanceof DOMNodeList ) {
1211  $newIterator = $contextNode;
1212  } elseif ( $contextNode instanceof DOMNode ) {
1213  if ( $contextNode->nodeType == XML_TEXT_NODE ) {
1214  $out .= $contextNode->nodeValue;
1215  } elseif ( $contextNode->nodeName == 'template' ) {
1216  # Double-brace expansion
1217  $xpath = new DOMXPath( $contextNode->ownerDocument );
1218  $titles = $xpath->query( 'title', $contextNode );
1219  $title = $titles->item( 0 );
1220  $parts = $xpath->query( 'part', $contextNode );
1221  if ( $flags & PPFrame::NO_TEMPLATES ) {
1222  $newIterator = $this->virtualBracketedImplode( '{{', '|', '}}', $title, $parts );
1223  } else {
1224  $lineStart = $contextNode->getAttribute( 'lineStart' );
1225  $params = [
1226  'title' => new PPNode_DOM( $title ),
1227  'parts' => new PPNode_DOM( $parts ),
1228  'lineStart' => $lineStart ];
1229  $ret = $this->parser->braceSubstitution( $params, $this );
1230  if ( isset( $ret['object'] ) ) {
1231  $newIterator = $ret['object'];
1232  } else {
1233  $out .= $ret['text'];
1234  }
1235  }
1236  } elseif ( $contextNode->nodeName == 'tplarg' ) {
1237  # Triple-brace expansion
1238  $xpath = new DOMXPath( $contextNode->ownerDocument );
1239  $titles = $xpath->query( 'title', $contextNode );
1240  $title = $titles->item( 0 );
1241  $parts = $xpath->query( 'part', $contextNode );
1242  if ( $flags & PPFrame::NO_ARGS ) {
1243  $newIterator = $this->virtualBracketedImplode( '{{{', '|', '}}}', $title, $parts );
1244  } else {
1245  $params = [
1246  'title' => new PPNode_DOM( $title ),
1247  'parts' => new PPNode_DOM( $parts ) ];
1248  $ret = $this->parser->argSubstitution( $params, $this );
1249  if ( isset( $ret['object'] ) ) {
1250  $newIterator = $ret['object'];
1251  } else {
1252  $out .= $ret['text'];
1253  }
1254  }
1255  } elseif ( $contextNode->nodeName == 'comment' ) {
1256  # HTML-style comment
1257  # Remove it in HTML, pre+remove and STRIP_COMMENTS modes
1258  # Not in RECOVER_COMMENTS mode (msgnw) though.
1259  if ( ( $this->parser->ot['html']
1260  || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() )
1261  || ( $flags & PPFrame::STRIP_COMMENTS )
1262  ) && !( $flags & PPFrame::RECOVER_COMMENTS )
1263  ) {
1264  $out .= '';
1265  } elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) {
1266  # Add a strip marker in PST mode so that pstPass2() can
1267  # run some old-fashioned regexes on the result.
1268  # Not in RECOVER_COMMENTS mode (extractSections) though.
1269  $out .= $this->parser->insertStripItem( $contextNode->textContent );
1270  } else {
1271  # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
1272  $out .= $contextNode->textContent;
1273  }
1274  } elseif ( $contextNode->nodeName == 'ignore' ) {
1275  # Output suppression used by <includeonly> etc.
1276  # OT_WIKI will only respect <ignore> in substed templates.
1277  # The other output types respect it unless NO_IGNORE is set.
1278  # extractSections() sets NO_IGNORE and so never respects it.
1279  if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] )
1280  || ( $flags & PPFrame::NO_IGNORE )
1281  ) {
1282  $out .= $contextNode->textContent;
1283  } else {
1284  $out .= '';
1285  }
1286  } elseif ( $contextNode->nodeName == 'ext' ) {
1287  # Extension tag
1288  $xpath = new DOMXPath( $contextNode->ownerDocument );
1289  $names = $xpath->query( 'name', $contextNode );
1290  $attrs = $xpath->query( 'attr', $contextNode );
1291  $inners = $xpath->query( 'inner', $contextNode );
1292  $closes = $xpath->query( 'close', $contextNode );
1293  if ( $flags & PPFrame::NO_TAGS ) {
1294  $s = '<' . $this->expand( $names->item( 0 ), $flags );
1295  if ( $attrs->length > 0 ) {
1296  $s .= $this->expand( $attrs->item( 0 ), $flags );
1297  }
1298  if ( $inners->length > 0 ) {
1299  $s .= '>' . $this->expand( $inners->item( 0 ), $flags );
1300  if ( $closes->length > 0 ) {
1301  $s .= $this->expand( $closes->item( 0 ), $flags );
1302  }
1303  } else {
1304  $s .= '/>';
1305  }
1306  $out .= $s;
1307  } else {
1308  $params = [
1309  'name' => new PPNode_DOM( $names->item( 0 ) ),
1310  'attr' => $attrs->length > 0 ? new PPNode_DOM( $attrs->item( 0 ) ) : null,
1311  'inner' => $inners->length > 0 ? new PPNode_DOM( $inners->item( 0 ) ) : null,
1312  'close' => $closes->length > 0 ? new PPNode_DOM( $closes->item( 0 ) ) : null,
1313  ];
1314  $out .= $this->parser->extensionSubstitution( $params, $this );
1315  }
1316  } elseif ( $contextNode->nodeName == 'h' ) {
1317  # Heading
1318  $s = $this->expand( $contextNode->childNodes, $flags );
1319 
1320  # Insert a heading marker only for <h> children of <root>
1321  # This is to stop extractSections from going over multiple tree levels
1322  if ( $contextNode->parentNode->nodeName == 'root' && $this->parser->ot['html'] ) {
1323  # Insert heading index marker
1324  $headingIndex = $contextNode->getAttribute( 'i' );
1325  $titleText = $this->title->getPrefixedDBkey();
1326  $this->parser->mHeadings[] = [ $titleText, $headingIndex ];
1327  $serial = count( $this->parser->mHeadings ) - 1;
1328  $marker = Parser::MARKER_PREFIX . "-h-$serial-" . Parser::MARKER_SUFFIX;
1329  $count = $contextNode->getAttribute( 'level' );
1330  $s = substr( $s, 0, $count ) . $marker . substr( $s, $count );
1331  $this->parser->mStripState->addGeneral( $marker, '' );
1332  }
1333  $out .= $s;
1334  } else {
1335  # Generic recursive expansion
1336  $newIterator = $contextNode->childNodes;
1337  }
1338  } else {
1339  throw new MWException( __METHOD__ . ': Invalid parameter type' );
1340  }
1341 
1342  if ( $newIterator !== false ) {
1343  if ( $newIterator instanceof PPNode_DOM ) {
1344  $newIterator = $newIterator->node;
1345  }
1346  $outStack[] = '';
1347  $iteratorStack[] = $newIterator;
1348  $indexStack[] = 0;
1349  } elseif ( $iteratorStack[$level] === false ) {
1350  // Return accumulated value to parent
1351  // With tail recursion
1352  while ( $iteratorStack[$level] === false && $level > 0 ) {
1353  $outStack[$level - 1] .= $out;
1354  array_pop( $outStack );
1355  array_pop( $iteratorStack );
1356  array_pop( $indexStack );
1357  $level--;
1358  }
1359  }
1360  }
1361  --$expansionDepth;
1362  return $outStack[0];
1363  }
1364 
1371  public function implodeWithFlags( $sep, $flags /*, ... */ ) {
1372  $args = array_slice( func_get_args(), 2 );
1373 
1374  $first = true;
1375  $s = '';
1376  foreach ( $args as $root ) {
1377  if ( $root instanceof PPNode_DOM ) {
1378  $root = $root->node;
1379  }
1380  if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
1381  $root = [ $root ];
1382  }
1383  foreach ( $root as $node ) {
1384  if ( $first ) {
1385  $first = false;
1386  } else {
1387  $s .= $sep;
1388  }
1389  $s .= $this->expand( $node, $flags );
1390  }
1391  }
1392  return $s;
1393  }
1394 
1403  public function implode( $sep /*, ... */ ) {
1404  $args = array_slice( func_get_args(), 1 );
1405 
1406  $first = true;
1407  $s = '';
1408  foreach ( $args as $root ) {
1409  if ( $root instanceof PPNode_DOM ) {
1410  $root = $root->node;
1411  }
1412  if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
1413  $root = [ $root ];
1414  }
1415  foreach ( $root as $node ) {
1416  if ( $first ) {
1417  $first = false;
1418  } else {
1419  $s .= $sep;
1420  }
1421  $s .= $this->expand( $node );
1422  }
1423  }
1424  return $s;
1425  }
1426 
1435  public function virtualImplode( $sep /*, ... */ ) {
1436  $args = array_slice( func_get_args(), 1 );
1437  $out = [];
1438  $first = true;
1439 
1440  foreach ( $args as $root ) {
1441  if ( $root instanceof PPNode_DOM ) {
1442  $root = $root->node;
1443  }
1444  if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
1445  $root = [ $root ];
1446  }
1447  foreach ( $root as $node ) {
1448  if ( $first ) {
1449  $first = false;
1450  } else {
1451  $out[] = $sep;
1452  }
1453  $out[] = $node;
1454  }
1455  }
1456  return $out;
1457  }
1458 
1467  public function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) {
1468  $args = array_slice( func_get_args(), 3 );
1469  $out = [ $start ];
1470  $first = true;
1471 
1472  foreach ( $args as $root ) {
1473  if ( $root instanceof PPNode_DOM ) {
1474  $root = $root->node;
1475  }
1476  if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
1477  $root = [ $root ];
1478  }
1479  foreach ( $root as $node ) {
1480  if ( $first ) {
1481  $first = false;
1482  } else {
1483  $out[] = $sep;
1484  }
1485  $out[] = $node;
1486  }
1487  }
1488  $out[] = $end;
1489  return $out;
1490  }
1491 
1492  public function __toString() {
1493  return 'frame{}';
1494  }
1495 
1496  public function getPDBK( $level = false ) {
1497  if ( $level === false ) {
1498  return $this->title->getPrefixedDBkey();
1499  } else {
1500  return isset( $this->titleCache[$level] ) ? $this->titleCache[$level] : false;
1501  }
1502  }
1503 
1507  public function getArguments() {
1508  return [];
1509  }
1510 
1514  public function getNumberedArguments() {
1515  return [];
1516  }
1517 
1521  public function getNamedArguments() {
1522  return [];
1523  }
1524 
1530  public function isEmpty() {
1531  return true;
1532  }
1533 
1538  public function getArgument( $name ) {
1539  return false;
1540  }
1541 
1548  public function loopCheck( $title ) {
1549  return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] );
1550  }
1551 
1557  public function isTemplate() {
1558  return false;
1559  }
1560 
1566  public function getTitle() {
1567  return $this->title;
1568  }
1569 
1575  public function setVolatile( $flag = true ) {
1576  $this->volatile = $flag;
1577  }
1578 
1584  public function isVolatile() {
1585  return $this->volatile;
1586  }
1587 
1593  public function setTTL( $ttl ) {
1594  if ( $ttl !== null && ( $this->ttl === null || $ttl < $this->ttl ) ) {
1595  $this->ttl = $ttl;
1596  }
1597  }
1598 
1604  public function getTTL() {
1605  return $this->ttl;
1606  }
1607 }
1608 
1613 // @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
1614 class PPTemplateFrame_DOM extends PPFrame_DOM {
1615  // @codingStandardsIgnoreEnd
1616 
1617  public $numberedArgs, $namedArgs;
1618 
1622  public $parent;
1623  public $numberedExpansionCache, $namedExpansionCache;
1624 
1632  public function __construct( $preprocessor, $parent = false, $numberedArgs = [],
1633  $namedArgs = [], $title = false
1634  ) {
1635  parent::__construct( $preprocessor );
1636 
1637  $this->parent = $parent;
1638  $this->numberedArgs = $numberedArgs;
1639  $this->namedArgs = $namedArgs;
1640  $this->title = $title;
1641  $pdbk = $title ? $title->getPrefixedDBkey() : false;
1642  $this->titleCache = $parent->titleCache;
1643  $this->titleCache[] = $pdbk;
1644  $this->loopCheckHash = /*clone*/ $parent->loopCheckHash;
1645  if ( $pdbk !== false ) {
1646  $this->loopCheckHash[$pdbk] = true;
1647  }
1648  $this->depth = $parent->depth + 1;
1649  $this->numberedExpansionCache = $this->namedExpansionCache = [];
1650  }
1651 
1652  public function __toString() {
1653  $s = 'tplframe{';
1654  $first = true;
1655  $args = $this->numberedArgs + $this->namedArgs;
1656  foreach ( $args as $name => $value ) {
1657  if ( $first ) {
1658  $first = false;
1659  } else {
1660  $s .= ', ';
1661  }
1662  $s .= "\"$name\":\"" .
1663  str_replace( '"', '\\"', $value->ownerDocument->saveXML( $value ) ) . '"';
1664  }
1665  $s .= '}';
1666  return $s;
1667  }
1668 
1676  public function cachedExpand( $key, $root, $flags = 0 ) {
1677  if ( isset( $this->parent->childExpansionCache[$key] ) ) {
1678  return $this->parent->childExpansionCache[$key];
1679  }
1680  $retval = $this->expand( $root, $flags );
1681  if ( !$this->isVolatile() ) {
1682  $this->parent->childExpansionCache[$key] = $retval;
1683  }
1684  return $retval;
1685  }
1686 
1692  public function isEmpty() {
1693  return !count( $this->numberedArgs ) && !count( $this->namedArgs );
1694  }
1695 
1696  public function getArguments() {
1697  $arguments = [];
1698  foreach ( array_merge(
1699  array_keys( $this->numberedArgs ),
1700  array_keys( $this->namedArgs ) ) as $key ) {
1701  $arguments[$key] = $this->getArgument( $key );
1702  }
1703  return $arguments;
1704  }
1705 
1706  public function getNumberedArguments() {
1707  $arguments = [];
1708  foreach ( array_keys( $this->numberedArgs ) as $key ) {
1709  $arguments[$key] = $this->getArgument( $key );
1710  }
1711  return $arguments;
1712  }
1713 
1714  public function getNamedArguments() {
1715  $arguments = [];
1716  foreach ( array_keys( $this->namedArgs ) as $key ) {
1717  $arguments[$key] = $this->getArgument( $key );
1718  }
1719  return $arguments;
1720  }
1721 
1726  public function getNumberedArgument( $index ) {
1727  if ( !isset( $this->numberedArgs[$index] ) ) {
1728  return false;
1729  }
1730  if ( !isset( $this->numberedExpansionCache[$index] ) ) {
1731  # No trimming for unnamed arguments
1732  $this->numberedExpansionCache[$index] = $this->parent->expand(
1733  $this->numberedArgs[$index],
1734  PPFrame::STRIP_COMMENTS
1735  );
1736  }
1737  return $this->numberedExpansionCache[$index];
1738  }
1739 
1744  public function getNamedArgument( $name ) {
1745  if ( !isset( $this->namedArgs[$name] ) ) {
1746  return false;
1747  }
1748  if ( !isset( $this->namedExpansionCache[$name] ) ) {
1749  # Trim named arguments post-expand, for backwards compatibility
1750  $this->namedExpansionCache[$name] = trim(
1751  $this->parent->expand( $this->namedArgs[$name], PPFrame::STRIP_COMMENTS ) );
1752  }
1753  return $this->namedExpansionCache[$name];
1754  }
1755 
1760  public function getArgument( $name ) {
1761  $text = $this->getNumberedArgument( $name );
1762  if ( $text === false ) {
1763  $text = $this->getNamedArgument( $name );
1764  }
1765  return $text;
1766  }
1767 
1773  public function isTemplate() {
1774  return true;
1775  }
1776 
1777  public function setVolatile( $flag = true ) {
1778  parent::setVolatile( $flag );
1779  $this->parent->setVolatile( $flag );
1780  }
1781 
1782  public function setTTL( $ttl ) {
1783  parent::setTTL( $ttl );
1784  $this->parent->setTTL( $ttl );
1785  }
1786 }
1787 
1792 // @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
1793 class PPCustomFrame_DOM extends PPFrame_DOM {
1794  // @codingStandardsIgnoreEnd
1795 
1796  public $args;
1797 
1798  public function __construct( $preprocessor, $args ) {
1799  parent::__construct( $preprocessor );
1800  $this->args = $args;
1801  }
1802 
1803  public function __toString() {
1804  $s = 'cstmframe{';
1805  $first = true;
1806  foreach ( $this->args as $name => $value ) {
1807  if ( $first ) {
1808  $first = false;
1809  } else {
1810  $s .= ', ';
1811  }
1812  $s .= "\"$name\":\"" .
1813  str_replace( '"', '\\"', $value->__toString() ) . '"';
1814  }
1815  $s .= '}';
1816  return $s;
1817  }
1818 
1822  public function isEmpty() {
1823  return !count( $this->args );
1824  }
1825 
1830  public function getArgument( $index ) {
1831  if ( !isset( $this->args[$index] ) ) {
1832  return false;
1833  }
1834  return $this->args[$index];
1835  }
1836 
1837  public function getArguments() {
1838  return $this->args;
1839  }
1840 }
1841 
1845 // @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
1846 class PPNode_DOM implements PPNode {
1847  // @codingStandardsIgnoreEnd
1848 
1852  public $node;
1853  public $xpath;
1854 
1855  public function __construct( $node, $xpath = false ) {
1856  $this->node = $node;
1857  }
1858 
1862  public function getXPath() {
1863  if ( $this->xpath === null ) {
1864  $this->xpath = new DOMXPath( $this->node->ownerDocument );
1865  }
1866  return $this->xpath;
1867  }
1868 
1869  public function __toString() {
1870  if ( $this->node instanceof DOMNodeList ) {
1871  $s = '';
1872  foreach ( $this->node as $node ) {
1873  $s .= $node->ownerDocument->saveXML( $node );
1874  }
1875  } else {
1876  $s = $this->node->ownerDocument->saveXML( $this->node );
1877  }
1878  return $s;
1879  }
1880 
1884  public function getChildren() {
1885  return $this->node->childNodes ? new self( $this->node->childNodes ) : false;
1886  }
1887 
1891  public function getFirstChild() {
1892  return $this->node->firstChild ? new self( $this->node->firstChild ) : false;
1893  }
1894 
1898  public function getNextSibling() {
1899  return $this->node->nextSibling ? new self( $this->node->nextSibling ) : false;
1900  }
1901 
1907  public function getChildrenOfType( $type ) {
1908  return new self( $this->getXPath()->query( $type, $this->node ) );
1909  }
1910 
1914  public function getLength() {
1915  if ( $this->node instanceof DOMNodeList ) {
1916  return $this->node->length;
1917  } else {
1918  return false;
1919  }
1920  }
1921 
1926  public function item( $i ) {
1927  $item = $this->node->item( $i );
1928  return $item ? new self( $item ) : false;
1929  }
1930 
1934  public function getName() {
1935  if ( $this->node instanceof DOMNodeList ) {
1936  return '#nodelist';
1937  } else {
1938  return $this->node->nodeName;
1939  }
1940  }
1941 
1951  public function splitArg() {
1952  $xpath = $this->getXPath();
1953  $names = $xpath->query( 'name', $this->node );
1954  $values = $xpath->query( 'value', $this->node );
1955  if ( !$names->length || !$values->length ) {
1956  throw new MWException( 'Invalid brace node passed to ' . __METHOD__ );
1957  }
1958  $name = $names->item( 0 );
1959  $index = $name->getAttribute( 'index' );
1960  return [
1961  'name' => new self( $name ),
1962  'index' => $index,
1963  'value' => new self( $values->item( 0 ) ) ];
1964  }
1965 
1973  public function splitExt() {
1974  $xpath = $this->getXPath();
1975  $names = $xpath->query( 'name', $this->node );
1976  $attrs = $xpath->query( 'attr', $this->node );
1977  $inners = $xpath->query( 'inner', $this->node );
1978  $closes = $xpath->query( 'close', $this->node );
1979  if ( !$names->length || !$attrs->length ) {
1980  throw new MWException( 'Invalid ext node passed to ' . __METHOD__ );
1981  }
1982  $parts = [
1983  'name' => new self( $names->item( 0 ) ),
1984  'attr' => new self( $attrs->item( 0 ) ) ];
1985  if ( $inners->length ) {
1986  $parts['inner'] = new self( $inners->item( 0 ) );
1987  }
1988  if ( $closes->length ) {
1989  $parts['close'] = new self( $closes->item( 0 ) );
1990  }
1991  return $parts;
1992  }
1993 
1999  public function splitHeading() {
2000  if ( $this->getName() !== 'h' ) {
2001  throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
2002  }
2003  return [
2004  'i' => $this->node->getAttribute( 'i' ),
2005  'level' => $this->node->getAttribute( 'level' ),
2006  'contents' => $this->getChildren()
2007  ];
2008  }
2009 }
Preprocessor_DOM\preprocessToXml
preprocessToXml( $text, $flags=0)
Definition: Preprocessor_DOM.php:194
part
in this case you re responsible for computing and outputting the entire conflict part
Definition: hooks.txt:1411
Preprocessor_DOM
Definition: Preprocessor_DOM.php:28
PPDPart
Definition: Preprocessor_DOM.php:975
type
This document describes the state of Postgres support in and is fairly well maintained The main code is very well while extensions are very hit and miss it is probably the most supported database after MySQL Much of the work in making MediaWiki database agnostic came about through the work of creating Postgres as and are nearing end of but without copying over all the usage comments General notes on the but these can almost always be programmed around *Although Postgres has a true BOOLEAN type
Definition: postgres.txt:22
PPNode_DOM
Definition: Preprocessor_DOM.php:1846
PPNode_DOM\item
item( $i)
Definition: Preprocessor_DOM.php:1926
PPNode_DOM\$xpath
$xpath
Definition: Preprocessor_DOM.php:1853
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
Preprocessor_DOM\$memoryLimit
$memoryLimit
Definition: Preprocessor_DOM.php:36
Preprocessor_DOM\$parser
Parser $parser
Definition: Preprocessor_DOM.php:34
PPNode_DOM\$node
DOMElement $node
Definition: Preprocessor_DOM.php:1852
captcha-old.count
count
Definition: captcha-old.py:249
text
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition: design.txt:12
$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:1963
wiki
Prior to maintenance scripts were a hodgepodge of code that had no cohesion or formal method of action Beginning maintenance scripts have been cleaned up to use a unified class Directory structure How to run a script How to write your own DIRECTORY STRUCTURE The maintenance directory of a MediaWiki installation contains several all of which have unique purposes HOW TO RUN A SCRIPT Ridiculously just call php someScript php that s in the top level maintenance directory if not default wiki
Definition: maintenance.txt:1
$s
$s
Definition: mergeMessageFileList.php:188
PPDStackElement
Definition: Preprocessor_DOM.php:876
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
so
c Accompany it with the information you received as to the offer to distribute corresponding source complete source code means all the source code for all modules it plus any associated interface definition plus the scripts used to control compilation and installation of the executable as a special the source code distributed need not include anything that is normally and so on of the operating system on which the executable unless that component itself accompanies the executable If distribution of executable or object code is made by offering access to copy from a designated then offering equivalent access to copy the source code from the same place counts as distribution of the source even though third parties are not compelled to copy the source along with the object code You may not or distribute the Program except as expressly provided under this License Any attempt otherwise to sublicense or distribute the Program is and will automatically terminate your rights under this License parties who have received or from you under this License will not have their licenses terminated so long as such parties remain in full compliance You are not required to accept this since you have not signed it nothing else grants you permission to modify or distribute the Program or its derivative works These actions are prohibited by law if you do not accept this License by modifying or distributing the you indicate your acceptance of this License to do so
Definition: COPYING.txt:185
PPNode_DOM\splitHeading
splitHeading()
Split a "<h>" node.
Definition: Preprocessor_DOM.php:1999
PPNode_DOM\getXPath
getXPath()
Definition: Preprocessor_DOM.php:1862
names
alter the names
Definition: COPYING.txt:329
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
Preprocessor
Definition: Preprocessor.php:29
PPNode_DOM\__construct
__construct( $node, $xpath=false)
Definition: Preprocessor_DOM.php:1855
title
to move a page</td >< td > &*You are moving the page across *A non empty talk page already exists under the new or *You uncheck the box below In those you will have to move or merge the page manually if desired</td >< td > be sure to &You are responsible for making sure that links continue to point where they are supposed to go Note that the page will &a page at the new title
Definition: All_system_messages.txt:2696
PPCustomFrame_DOM
Expansion frame with custom arguments.
Definition: Preprocessor_DOM.php:1793
PPNode_DOM\getName
getName()
Definition: Preprocessor_DOM.php:1934
MWException
MediaWiki exception.
Definition: MWException.php:26
a
</source > ! result< div class="mw-highlight mw-content-ltr" dir="ltr">< pre >< span ></span >< span class="kd"> var</span >< span class="nx"> a</span >< span class="p"></span ></pre ></div > ! end ! test Multiline< source/> in lists !input *< source > a b</source > *foo< source > a b</source > ! html< ul >< li >< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > a b</pre ></div ></li ></ul >< ul >< li > foo< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > a b</pre ></div ></li ></ul > ! html tidy< ul >< li >< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > a b</pre ></div ></li ></ul >< ul >< li > foo< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > a b</pre ></div ></li ></ul > ! end ! test Custom attributes !input< source lang="javascript" id="foo" class="bar" dir="rtl" style="font-size: larger;"> var a
Definition: parserTests.txt:89
PPNode_DOM\getLength
getLength()
Definition: Preprocessor_DOM.php:1914
PPCustomFrame_DOM\getArgument
getArgument( $index)
Definition: Preprocessor_DOM.php:1830
$matches
$matches
Definition: NoLocalSettings.php:24
Preprocessor_DOM\__construct
__construct( $parser)
Definition: Preprocessor_DOM.php:40
PPNode
There are three types of nodes:
Definition: Preprocessor.php:359
PPNode_DOM\getChildren
getChildren()
Definition: Preprocessor_DOM.php:1884
value
$status value
Definition: SyntaxHighlight.class.php:315
Preprocessor_DOM\newCustomFrame
newCustomFrame( $args)
Definition: Preprocessor_DOM.php:64
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
PPNode_DOM\getChildrenOfType
getChildrenOfType( $type)
Definition: Preprocessor_DOM.php:1907
$wgDisableLangConversion
$wgDisableLangConversion
Whether to enable language variant conversion.
Definition: DefaultSettings.php:3032
Preprocessor_DOM\memCheck
memCheck()
Definition: Preprocessor_DOM.php:114
root
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 and we might be restricted by PHP settings such as safe mode or open_basedir We cannot assume that the software even has read access anywhere useful Many shared hosts run all users web applications under the same so they can t rely on Unix and must forbid reads to even standard directories like tmp lest users read each others files We cannot assume that the user has the ability to install or run any programs not written as web accessible PHP scripts Since anything that works on cheap shared hosting will work if you have shell or root access MediaWiki s design is based around catering to the lowest common denominator Although we support higher end setups as the way many things work by default is tailored toward shared hosting These defaults are unconventional from the point of view of and they certainly aren t ideal for someone who s installing MediaWiki as root
Definition: distributors.txt:39
$value
$value
Definition: styleTest.css.php:45
PPFrame_DOM
An expansion frame, used as a context to expand the result of preprocessToObj()
Definition: Preprocessor_DOM.php:996
PPDStack
Stack class to help Preprocessor::preprocessToObj()
Definition: Preprocessor_DOM.php:787
PPNode_DOM\__toString
__toString()
Definition: Preprocessor_DOM.php:1869
$args
if( $line===false) $args
Definition: cdb.php:63
Preprocessor\cacheSetTree
cacheSetTree( $text, $flags, $tree)
Store a document tree in the cache.
Definition: Preprocessor.php:67
Preprocessor_DOM\CACHE_PREFIX
const CACHE_PREFIX
Definition: Preprocessor_DOM.php:38
PPCustomFrame_DOM\isEmpty
isEmpty()
Definition: Preprocessor_DOM.php:1822
PPNode_DOM\splitArg
splitArg()
Split a "<part>" node into an associative array containing:
Definition: Preprocessor_DOM.php:1951
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
Preprocessor_DOM\newPartNodeArray
newPartNodeArray( $values)
Definition: Preprocessor_DOM.php:73
Preprocessor_DOM\newFrame
newFrame()
Definition: Preprocessor_DOM.php:56
PPNode_DOM\getFirstChild
getFirstChild()
Definition: Preprocessor_DOM.php:1891
captcha-old.parser
parser
Definition: captcha-old.py:210
PPCustomFrame_DOM\getArguments
getArguments()
Definition: Preprocessor_DOM.php:1837
name
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at name
Definition: design.txt:12
Preprocessor\cacheGetTree
cacheGetTree( $text, $flags)
Attempt to load a precomputed document tree for some given wikitext from the cache.
Definition: Preprocessor.php:96
captcha-old.args
args
Definition: captcha-old.py:227
PPNode_DOM\splitExt
splitExt()
Split an "<ext>" node into an associative array containing name, attr, inner and close All values in ...
Definition: Preprocessor_DOM.php:1973
$flags
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2801
Preprocessor_DOM\preprocessToObj
preprocessToObj( $text, $flags=0)
Preprocess some wikitext and return the document tree.
Definition: Preprocessor_DOM.php:150
PPNode_DOM\getNextSibling
getNextSibling()
Definition: Preprocessor_DOM.php:1898
$type
$type
Definition: testCompression.php:48