MediaWiki  REL1_31
Preprocessor_DOM.php
Go to the documentation of this file.
1 <?php
27 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
29 
33  public $parser;
34 
35  public $memoryLimit;
36 
37  const CACHE_PREFIX = 'preprocess-xml';
38 
39  public function __construct( $parser ) {
40  $this->parser = $parser;
41  $mem = ini_get( 'memory_limit' );
42  $this->memoryLimit = false;
43  if ( strval( $mem ) !== '' && $mem != -1 ) {
44  if ( preg_match( '/^\d+$/', $mem ) ) {
45  $this->memoryLimit = $mem;
46  } elseif ( preg_match( '/^(\d+)M$/i', $mem, $m ) ) {
47  $this->memoryLimit = $m[1] * 1048576;
48  }
49  }
50  }
51 
55  public function newFrame() {
56  return new PPFrame_DOM( $this );
57  }
58 
63  public function newCustomFrame( $args ) {
64  return new PPCustomFrame_DOM( $this, $args );
65  }
66 
72  public function newPartNodeArray( $values ) {
73  // NOTE: DOM manipulation is slower than building & parsing XML! (or so Tim sais)
74  $xml = "<list>";
75 
76  foreach ( $values as $k => $val ) {
77  if ( is_int( $k ) ) {
78  $xml .= "<part><name index=\"$k\"/><value>"
79  . htmlspecialchars( $val ) . "</value></part>";
80  } else {
81  $xml .= "<part><name>" . htmlspecialchars( $k )
82  . "</name>=<value>" . htmlspecialchars( $val ) . "</value></part>";
83  }
84  }
85 
86  $xml .= "</list>";
87 
88  $dom = new DOMDocument();
89  Wikimedia\suppressWarnings();
90  $result = $dom->loadXML( $xml );
91  Wikimedia\restoreWarnings();
92  if ( !$result ) {
93  // Try running the XML through UtfNormal to get rid of invalid characters
94  $xml = UtfNormal\Validator::cleanUp( $xml );
95  // 1 << 19 == XML_PARSE_HUGE, needed so newer versions of libxml2
96  // don't barf when the XML is >256 levels deep
97  $result = $dom->loadXML( $xml, 1 << 19 );
98  }
99 
100  if ( !$result ) {
101  throw new MWException( 'Parameters passed to ' . __METHOD__ . ' result in invalid XML' );
102  }
103 
104  $root = $dom->documentElement;
105  $node = new PPNode_DOM( $root->childNodes );
106  return $node;
107  }
108 
113  public function memCheck() {
114  if ( $this->memoryLimit === false ) {
115  return true;
116  }
117  $usage = memory_get_usage();
118  if ( $usage > $this->memoryLimit * 0.9 ) {
119  $limit = intval( $this->memoryLimit * 0.9 / 1048576 + 0.5 );
120  throw new MWException( "Preprocessor hit 90% memory limit ($limit MB)" );
121  }
122  return $usage <= $this->memoryLimit * 0.8;
123  }
124 
149  public function preprocessToObj( $text, $flags = 0 ) {
150  $xml = $this->cacheGetTree( $text, $flags );
151  if ( $xml === false ) {
152  $xml = $this->preprocessToXml( $text, $flags );
153  $this->cacheSetTree( $text, $flags, $xml );
154  }
155 
156  // Fail if the number of elements exceeds acceptable limits
157  // Do not attempt to generate the DOM
158  $this->parser->mGeneratedPPNodeCount += substr_count( $xml, '<' );
159  $max = $this->parser->mOptions->getMaxGeneratedPPNodeCount();
160  if ( $this->parser->mGeneratedPPNodeCount > $max ) {
161  // if ( $cacheable ) { ... }
162  throw new MWException( __METHOD__ . ': generated node count limit exceeded' );
163  }
164 
165  $dom = new DOMDocument;
166  Wikimedia\suppressWarnings();
167  $result = $dom->loadXML( $xml );
168  Wikimedia\restoreWarnings();
169  if ( !$result ) {
170  // Try running the XML through UtfNormal to get rid of invalid characters
171  $xml = UtfNormal\Validator::cleanUp( $xml );
172  // 1 << 19 == XML_PARSE_HUGE, needed so newer versions of libxml2
173  // don't barf when the XML is >256 levels deep.
174  $result = $dom->loadXML( $xml, 1 << 19 );
175  }
176  if ( $result ) {
177  $obj = new PPNode_DOM( $dom->documentElement );
178  }
179 
180  // if ( $cacheable ) { ... }
181 
182  if ( !$result ) {
183  throw new MWException( __METHOD__ . ' generated invalid XML' );
184  }
185  return $obj;
186  }
187 
193  public function preprocessToXml( $text, $flags = 0 ) {
195 
196  $forInclusion = $flags & Parser::PTD_FOR_INCLUSION;
197 
198  $xmlishElements = $this->parser->getStripList();
199  $xmlishAllowMissingEndTag = [ 'includeonly', 'noinclude', 'onlyinclude' ];
200  $enableOnlyinclude = false;
201  if ( $forInclusion ) {
202  $ignoredTags = [ 'includeonly', '/includeonly' ];
203  $ignoredElements = [ 'noinclude' ];
204  $xmlishElements[] = 'noinclude';
205  if ( strpos( $text, '<onlyinclude>' ) !== false
206  && strpos( $text, '</onlyinclude>' ) !== false
207  ) {
208  $enableOnlyinclude = true;
209  }
210  } else {
211  $ignoredTags = [ 'noinclude', '/noinclude', 'onlyinclude', '/onlyinclude' ];
212  $ignoredElements = [ 'includeonly' ];
213  $xmlishElements[] = 'includeonly';
214  }
215  $xmlishRegex = implode( '|', array_merge( $xmlishElements, $ignoredTags ) );
216 
217  // Use "A" modifier (anchored) instead of "^", because ^ doesn't work with an offset
218  $elementsRegex = "~($xmlishRegex)(?:\s|\/>|>)|(!--)~iA";
219 
220  $stack = new PPDStack;
221 
222  $searchBase = "[{<\n"; # }
223  if ( !$wgDisableLangConversion ) {
224  $searchBase .= '-';
225  }
226 
227  // For fast reverse searches
228  $revText = strrev( $text );
229  $lengthText = strlen( $text );
230 
231  // Input pointer, starts out pointing to a pseudo-newline before the start
232  $i = 0;
233  // Current accumulator
234  $accum =& $stack->getAccum();
235  $accum = '<root>';
236  // True to find equals signs in arguments
237  $findEquals = false;
238  // True to take notice of pipe characters
239  $findPipe = false;
240  $headingIndex = 1;
241  // True if $i is inside a possible heading
242  $inHeading = false;
243  // True if there are no more greater-than (>) signs right of $i
244  $noMoreGT = false;
245  // Map of tag name => true if there are no more closing tags of given type right of $i
246  $noMoreClosingTag = [];
247  // True to ignore all input up to the next <onlyinclude>
248  $findOnlyinclude = $enableOnlyinclude;
249  // Do a line-start run without outputting an LF character
250  $fakeLineStart = true;
251 
252  while ( true ) {
253  // $this->memCheck();
254 
255  if ( $findOnlyinclude ) {
256  // Ignore all input up to the next <onlyinclude>
257  $startPos = strpos( $text, '<onlyinclude>', $i );
258  if ( $startPos === false ) {
259  // Ignored section runs to the end
260  $accum .= '<ignore>' . htmlspecialchars( substr( $text, $i ) ) . '</ignore>';
261  break;
262  }
263  $tagEndPos = $startPos + strlen( '<onlyinclude>' ); // past-the-end
264  $accum .= '<ignore>' . htmlspecialchars( substr( $text, $i, $tagEndPos - $i ) ) . '</ignore>';
265  $i = $tagEndPos;
266  $findOnlyinclude = false;
267  }
268 
269  if ( $fakeLineStart ) {
270  $found = 'line-start';
271  $curChar = '';
272  } else {
273  # Find next opening brace, closing brace or pipe
274  $search = $searchBase;
275  if ( $stack->top === false ) {
276  $currentClosing = '';
277  } else {
278  $currentClosing = $stack->top->close;
279  $search .= $currentClosing;
280  }
281  if ( $findPipe ) {
282  $search .= '|';
283  }
284  if ( $findEquals ) {
285  // First equals will be for the template
286  $search .= '=';
287  }
288  $rule = null;
289  # Output literal section, advance input counter
290  $literalLength = strcspn( $text, $search, $i );
291  if ( $literalLength > 0 ) {
292  $accum .= htmlspecialchars( substr( $text, $i, $literalLength ) );
293  $i += $literalLength;
294  }
295  if ( $i >= $lengthText ) {
296  if ( $currentClosing == "\n" ) {
297  // Do a past-the-end run to finish off the heading
298  $curChar = '';
299  $found = 'line-end';
300  } else {
301  # All done
302  break;
303  }
304  } else {
305  $curChar = $curTwoChar = $text[$i];
306  if ( ( $i + 1 ) < $lengthText ) {
307  $curTwoChar .= $text[$i + 1];
308  }
309  if ( $curChar == '|' ) {
310  $found = 'pipe';
311  } elseif ( $curChar == '=' ) {
312  $found = 'equals';
313  } elseif ( $curChar == '<' ) {
314  $found = 'angle';
315  } elseif ( $curChar == "\n" ) {
316  if ( $inHeading ) {
317  $found = 'line-end';
318  } else {
319  $found = 'line-start';
320  }
321  } elseif ( $curTwoChar == $currentClosing ) {
322  $found = 'close';
323  $curChar = $curTwoChar;
324  } elseif ( $curChar == $currentClosing ) {
325  $found = 'close';
326  } elseif ( isset( $this->rules[$curTwoChar] ) ) {
327  $curChar = $curTwoChar;
328  $found = 'open';
329  $rule = $this->rules[$curChar];
330  } elseif ( isset( $this->rules[$curChar] ) ) {
331  $found = 'open';
332  $rule = $this->rules[$curChar];
333  } else {
334  # Some versions of PHP have a strcspn which stops on
335  # null characters; ignore these and continue.
336  # We also may get '-' and '}' characters here which
337  # don't match -{ or $currentClosing. Add these to
338  # output and continue.
339  if ( $curChar == '-' || $curChar == '}' ) {
340  $accum .= $curChar;
341  }
342  ++$i;
343  continue;
344  }
345  }
346  }
347 
348  if ( $found == 'angle' ) {
349  $matches = false;
350  // Handle </onlyinclude>
351  if ( $enableOnlyinclude
352  && substr( $text, $i, strlen( '</onlyinclude>' ) ) == '</onlyinclude>'
353  ) {
354  $findOnlyinclude = true;
355  continue;
356  }
357 
358  // Determine element name
359  if ( !preg_match( $elementsRegex, $text, $matches, 0, $i + 1 ) ) {
360  // Element name missing or not listed
361  $accum .= '&lt;';
362  ++$i;
363  continue;
364  }
365  // Handle comments
366  if ( isset( $matches[2] ) && $matches[2] == '!--' ) {
367  // To avoid leaving blank lines, when a sequence of
368  // space-separated comments is both preceded and followed by
369  // a newline (ignoring spaces), then
370  // trim leading and trailing spaces and the trailing newline.
371 
372  // Find the end
373  $endPos = strpos( $text, '-->', $i + 4 );
374  if ( $endPos === false ) {
375  // Unclosed comment in input, runs to end
376  $inner = substr( $text, $i );
377  $accum .= '<comment>' . htmlspecialchars( $inner ) . '</comment>';
378  $i = $lengthText;
379  } else {
380  // Search backwards for leading whitespace
381  $wsStart = $i ? ( $i - strspn( $revText, " \t", $lengthText - $i ) ) : 0;
382 
383  // Search forwards for trailing whitespace
384  // $wsEnd will be the position of the last space (or the '>' if there's none)
385  $wsEnd = $endPos + 2 + strspn( $text, " \t", $endPos + 3 );
386 
387  // Keep looking forward as long as we're finding more
388  // comments.
389  $comments = [ [ $wsStart, $wsEnd ] ];
390  while ( substr( $text, $wsEnd + 1, 4 ) == '<!--' ) {
391  $c = strpos( $text, '-->', $wsEnd + 4 );
392  if ( $c === false ) {
393  break;
394  }
395  $c = $c + 2 + strspn( $text, " \t", $c + 3 );
396  $comments[] = [ $wsEnd + 1, $c ];
397  $wsEnd = $c;
398  }
399 
400  // Eat the line if possible
401  // TODO: This could theoretically be done if $wsStart == 0, i.e. for comments at
402  // the overall start. That's not how Sanitizer::removeHTMLcomments() did it, but
403  // it's a possible beneficial b/c break.
404  if ( $wsStart > 0 && substr( $text, $wsStart - 1, 1 ) == "\n"
405  && substr( $text, $wsEnd + 1, 1 ) == "\n"
406  ) {
407  // Remove leading whitespace from the end of the accumulator
408  // Sanity check first though
409  $wsLength = $i - $wsStart;
410  if ( $wsLength > 0
411  && strspn( $accum, " \t", -$wsLength ) === $wsLength
412  ) {
413  $accum = substr( $accum, 0, -$wsLength );
414  }
415 
416  // Dump all but the last comment to the accumulator
417  foreach ( $comments as $j => $com ) {
418  $startPos = $com[0];
419  $endPos = $com[1] + 1;
420  if ( $j == ( count( $comments ) - 1 ) ) {
421  break;
422  }
423  $inner = substr( $text, $startPos, $endPos - $startPos );
424  $accum .= '<comment>' . htmlspecialchars( $inner ) . '</comment>';
425  }
426 
427  // Do a line-start run next time to look for headings after the comment
428  $fakeLineStart = true;
429  } else {
430  // No line to eat, just take the comment itself
431  $startPos = $i;
432  $endPos += 2;
433  }
434 
435  if ( $stack->top ) {
436  $part = $stack->top->getCurrentPart();
437  if ( !( isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 ) ) {
438  $part->visualEnd = $wsStart;
439  }
440  // Else comments abutting, no change in visual end
441  $part->commentEnd = $endPos;
442  }
443  $i = $endPos + 1;
444  $inner = substr( $text, $startPos, $endPos - $startPos + 1 );
445  $accum .= '<comment>' . htmlspecialchars( $inner ) . '</comment>';
446  }
447  continue;
448  }
449  $name = $matches[1];
450  $lowerName = strtolower( $name );
451  $attrStart = $i + strlen( $name ) + 1;
452 
453  // Find end of tag
454  $tagEndPos = $noMoreGT ? false : strpos( $text, '>', $attrStart );
455  if ( $tagEndPos === false ) {
456  // Infinite backtrack
457  // Disable tag search to prevent worst-case O(N^2) performance
458  $noMoreGT = true;
459  $accum .= '&lt;';
460  ++$i;
461  continue;
462  }
463 
464  // Handle ignored tags
465  if ( in_array( $lowerName, $ignoredTags ) ) {
466  $accum .= '<ignore>'
467  . htmlspecialchars( substr( $text, $i, $tagEndPos - $i + 1 ) )
468  . '</ignore>';
469  $i = $tagEndPos + 1;
470  continue;
471  }
472 
473  $tagStartPos = $i;
474  if ( $text[$tagEndPos - 1] == '/' ) {
475  $attrEnd = $tagEndPos - 1;
476  $inner = null;
477  $i = $tagEndPos + 1;
478  $close = '';
479  } else {
480  $attrEnd = $tagEndPos;
481  // Find closing tag
482  if (
483  !isset( $noMoreClosingTag[$name] ) &&
484  preg_match( "/<\/" . preg_quote( $name, '/' ) . "\s*>/i",
485  $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 )
486  ) {
487  $inner = substr( $text, $tagEndPos + 1, $matches[0][1] - $tagEndPos - 1 );
488  $i = $matches[0][1] + strlen( $matches[0][0] );
489  $close = '<close>' . htmlspecialchars( $matches[0][0] ) . '</close>';
490  } else {
491  // No end tag
492  if ( in_array( $name, $xmlishAllowMissingEndTag ) ) {
493  // Let it run out to the end of the text.
494  $inner = substr( $text, $tagEndPos + 1 );
495  $i = $lengthText;
496  $close = '';
497  } else {
498  // Don't match the tag, treat opening tag as literal and resume parsing.
499  $i = $tagEndPos + 1;
500  $accum .= htmlspecialchars( substr( $text, $tagStartPos, $tagEndPos + 1 - $tagStartPos ) );
501  // Cache results, otherwise we have O(N^2) performance for input like <foo><foo><foo>...
502  $noMoreClosingTag[$name] = true;
503  continue;
504  }
505  }
506  }
507  // <includeonly> and <noinclude> just become <ignore> tags
508  if ( in_array( $lowerName, $ignoredElements ) ) {
509  $accum .= '<ignore>' . htmlspecialchars( substr( $text, $tagStartPos, $i - $tagStartPos ) )
510  . '</ignore>';
511  continue;
512  }
513 
514  $accum .= '<ext>';
515  if ( $attrEnd <= $attrStart ) {
516  $attr = '';
517  } else {
518  $attr = substr( $text, $attrStart, $attrEnd - $attrStart );
519  }
520  $accum .= '<name>' . htmlspecialchars( $name ) . '</name>' .
521  // Note that the attr element contains the whitespace between name and attribute,
522  // this is necessary for precise reconstruction during pre-save transform.
523  '<attr>' . htmlspecialchars( $attr ) . '</attr>';
524  if ( $inner !== null ) {
525  $accum .= '<inner>' . htmlspecialchars( $inner ) . '</inner>';
526  }
527  $accum .= $close . '</ext>';
528  } elseif ( $found == 'line-start' ) {
529  // Is this the start of a heading?
530  // Line break belongs before the heading element in any case
531  if ( $fakeLineStart ) {
532  $fakeLineStart = false;
533  } else {
534  $accum .= $curChar;
535  $i++;
536  }
537 
538  $count = strspn( $text, '=', $i, 6 );
539  if ( $count == 1 && $findEquals ) {
540  // DWIM: This looks kind of like a name/value separator.
541  // Let's let the equals handler have it and break the
542  // potential heading. This is heuristic, but AFAICT the
543  // methods for completely correct disambiguation are very
544  // complex.
545  } elseif ( $count > 0 ) {
546  $piece = [
547  'open' => "\n",
548  'close' => "\n",
549  'parts' => [ new PPDPart( str_repeat( '=', $count ) ) ],
550  'startPos' => $i,
551  'count' => $count ];
552  $stack->push( $piece );
553  $accum =& $stack->getAccum();
554  $stackFlags = $stack->getFlags();
555  if ( isset( $stackFlags['findEquals'] ) ) {
556  $findEquals = $stackFlags['findEquals'];
557  }
558  if ( isset( $stackFlags['findPipe'] ) ) {
559  $findPipe = $stackFlags['findPipe'];
560  }
561  if ( isset( $stackFlags['inHeading'] ) ) {
562  $inHeading = $stackFlags['inHeading'];
563  }
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  $stackFlags = $stack->getFlags();
614  if ( isset( $stackFlags['findEquals'] ) ) {
615  $findEquals = $stackFlags['findEquals'];
616  }
617  if ( isset( $stackFlags['findPipe'] ) ) {
618  $findPipe = $stackFlags['findPipe'];
619  }
620  if ( isset( $stackFlags['inHeading'] ) ) {
621  $inHeading = $stackFlags['inHeading'];
622  }
623 
624  // Append the result to the enclosing accumulator
625  $accum .= $element;
626  // Note that we do NOT increment the input pointer.
627  // This is because the closing linebreak could be the opening linebreak of
628  // another heading. Infinite loops are avoided because the next iteration MUST
629  // hit the heading open case above, which unconditionally increments the
630  // input pointer.
631  } elseif ( $found == 'open' ) {
632  # count opening brace characters
633  $curLen = strlen( $curChar );
634  $count = ( $curLen > 1 ) ?
635  # allow the final character to repeat
636  strspn( $text, $curChar[$curLen - 1], $i + 1 ) + 1 :
637  strspn( $text, $curChar, $i );
638 
639  $savedPrefix = '';
640  $lineStart = ( $i > 0 && $text[$i - 1] == "\n" );
641 
642  if ( $curChar === "-{" && $count > $curLen ) {
643  // -{ => {{ transition because rightmost wins
644  $savedPrefix = '-';
645  $i++;
646  $curChar = '{';
647  $count--;
648  $rule = $this->rules[$curChar];
649  }
650 
651  # we need to add to stack only if opening brace count is enough for one of the rules
652  if ( $count >= $rule['min'] ) {
653  # Add it to the stack
654  $piece = [
655  'open' => $curChar,
656  'close' => $rule['end'],
657  'savedPrefix' => $savedPrefix,
658  'count' => $count,
659  'lineStart' => $lineStart,
660  ];
661 
662  $stack->push( $piece );
663  $accum =& $stack->getAccum();
664  $stackFlags = $stack->getFlags();
665  if ( isset( $stackFlags['findEquals'] ) ) {
666  $findEquals = $stackFlags['findEquals'];
667  }
668  if ( isset( $stackFlags['findPipe'] ) ) {
669  $findPipe = $stackFlags['findPipe'];
670  }
671  if ( isset( $stackFlags['inHeading'] ) ) {
672  $inHeading = $stackFlags['inHeading'];
673  }
674  } else {
675  # Add literal brace(s)
676  $accum .= htmlspecialchars( $savedPrefix . str_repeat( $curChar, $count ) );
677  }
678  $i += $count;
679  } elseif ( $found == 'close' ) {
680  $piece = $stack->top;
681  # lets check if there are enough characters for closing brace
682  $maxCount = $piece->count;
683  if ( $piece->close === '}-' && $curChar === '}' ) {
684  $maxCount--; # don't try to match closing '-' as a '}'
685  }
686  $curLen = strlen( $curChar );
687  $count = ( $curLen > 1 ) ? $curLen :
688  strspn( $text, $curChar, $i, $maxCount );
689 
690  # check for maximum matching characters (if there are 5 closing
691  # characters, we will probably need only 3 - depending on the rules)
692  $rule = $this->rules[$piece->open];
693  if ( $count > $rule['max'] ) {
694  # The specified maximum exists in the callback array, unless the caller
695  # has made an error
696  $matchingCount = $rule['max'];
697  } else {
698  # Count is less than the maximum
699  # Skip any gaps in the callback array to find the true largest match
700  # Need to use array_key_exists not isset because the callback can be null
701  $matchingCount = $count;
702  while ( $matchingCount > 0 && !array_key_exists( $matchingCount, $rule['names'] ) ) {
703  --$matchingCount;
704  }
705  }
706 
707  if ( $matchingCount <= 0 ) {
708  # No matching element found in callback array
709  # Output a literal closing brace and continue
710  $endText = substr( $text, $i, $count );
711  $accum .= htmlspecialchars( $endText );
712  $i += $count;
713  continue;
714  }
715  $name = $rule['names'][$matchingCount];
716  if ( $name === null ) {
717  // No element, just literal text
718  $endText = substr( $text, $i, $matchingCount );
719  $element = $piece->breakSyntax( $matchingCount ) . $endText;
720  } else {
721  # Create XML element
722  # Note: $parts is already XML, does not need to be encoded further
723  $parts = $piece->parts;
724  $title = $parts[0]->out;
725  unset( $parts[0] );
726 
727  # The invocation is at the start of the line if lineStart is set in
728  # the stack, and all opening brackets are used up.
729  if ( $maxCount == $matchingCount &&
730  !empty( $piece->lineStart ) &&
731  strlen( $piece->savedPrefix ) == 0 ) {
732  $attr = ' lineStart="1"';
733  } else {
734  $attr = '';
735  }
736 
737  $element = "<$name$attr>";
738  $element .= "<title>$title</title>";
739  $argIndex = 1;
740  foreach ( $parts as $part ) {
741  if ( isset( $part->eqpos ) ) {
742  $argName = substr( $part->out, 0, $part->eqpos );
743  $argValue = substr( $part->out, $part->eqpos + 1 );
744  $element .= "<part><name>$argName</name>=<value>$argValue</value></part>";
745  } else {
746  $element .= "<part><name index=\"$argIndex\" /><value>{$part->out}</value></part>";
747  $argIndex++;
748  }
749  }
750  $element .= "</$name>";
751  }
752 
753  # Advance input pointer
754  $i += $matchingCount;
755 
756  # Unwind the stack
757  $stack->pop();
758  $accum =& $stack->getAccum();
759 
760  # Re-add the old stack element if it still has unmatched opening characters remaining
761  if ( $matchingCount < $piece->count ) {
762  $piece->parts = [ new PPDPart ];
763  $piece->count -= $matchingCount;
764  # do we still qualify for any callback with remaining count?
765  $min = $this->rules[$piece->open]['min'];
766  if ( $piece->count >= $min ) {
767  $stack->push( $piece );
768  $accum =& $stack->getAccum();
769  } elseif ( $piece->count == 1 && $piece->open === '{' && $piece->savedPrefix === '-' ) {
770  $piece->savedPrefix = '';
771  $piece->open = '-{';
772  $piece->count = 2;
773  $piece->close = $this->rules[$piece->open]['end'];
774  $stack->push( $piece );
775  $accum =& $stack->getAccum();
776  } else {
777  $s = substr( $piece->open, 0, -1 );
778  $s .= str_repeat(
779  substr( $piece->open, -1 ),
780  $piece->count - strlen( $s )
781  );
782  $accum .= $piece->savedPrefix . $s;
783  }
784  } elseif ( $piece->savedPrefix !== '' ) {
785  $accum .= $piece->savedPrefix;
786  }
787 
788  $stackFlags = $stack->getFlags();
789  if ( isset( $stackFlags['findEquals'] ) ) {
790  $findEquals = $stackFlags['findEquals'];
791  }
792  if ( isset( $stackFlags['findPipe'] ) ) {
793  $findPipe = $stackFlags['findPipe'];
794  }
795  if ( isset( $stackFlags['inHeading'] ) ) {
796  $inHeading = $stackFlags['inHeading'];
797  }
798 
799  # Add XML element to the enclosing accumulator
800  $accum .= $element;
801  } elseif ( $found == 'pipe' ) {
802  $findEquals = true; // shortcut for getFlags()
803  $stack->addPart();
804  $accum =& $stack->getAccum();
805  ++$i;
806  } elseif ( $found == 'equals' ) {
807  $findEquals = false; // shortcut for getFlags()
808  $stack->getCurrentPart()->eqpos = strlen( $accum );
809  $accum .= '=';
810  ++$i;
811  }
812  }
813 
814  # Output any remaining unclosed brackets
815  foreach ( $stack->stack as $piece ) {
816  $stack->rootAccum .= $piece->breakSyntax();
817  }
818  $stack->rootAccum .= '</root>';
819  $xml = $stack->rootAccum;
820 
821  return $xml;
822  }
823 }
824 
829 class PPDStack {
830  public $stack, $rootAccum;
831 
835  public $top;
836  public $out;
837  public $elementClass = PPDStackElement::class;
838 
839  public static $false = false;
840 
841  public function __construct() {
842  $this->stack = [];
843  $this->top = false;
844  $this->rootAccum = '';
845  $this->accum =& $this->rootAccum;
846  }
847 
851  public function count() {
852  return count( $this->stack );
853  }
854 
855  public function &getAccum() {
856  return $this->accum;
857  }
858 
859  public function getCurrentPart() {
860  if ( $this->top === false ) {
861  return false;
862  } else {
863  return $this->top->getCurrentPart();
864  }
865  }
866 
867  public function push( $data ) {
868  if ( $data instanceof $this->elementClass ) {
869  $this->stack[] = $data;
870  } else {
871  $class = $this->elementClass;
872  $this->stack[] = new $class( $data );
873  }
874  $this->top = $this->stack[count( $this->stack ) - 1];
875  $this->accum =& $this->top->getAccum();
876  }
877 
878  public function pop() {
879  if ( !count( $this->stack ) ) {
880  throw new MWException( __METHOD__ . ': no elements remaining' );
881  }
882  $temp = array_pop( $this->stack );
883 
884  if ( count( $this->stack ) ) {
885  $this->top = $this->stack[count( $this->stack ) - 1];
886  $this->accum =& $this->top->getAccum();
887  } else {
888  $this->top = self::$false;
889  $this->accum =& $this->rootAccum;
890  }
891  return $temp;
892  }
893 
894  public function addPart( $s = '' ) {
895  $this->top->addPart( $s );
896  $this->accum =& $this->top->getAccum();
897  }
898 
902  public function getFlags() {
903  if ( !count( $this->stack ) ) {
904  return [
905  'findEquals' => false,
906  'findPipe' => false,
907  'inHeading' => false,
908  ];
909  } else {
910  return $this->top->getFlags();
911  }
912  }
913 }
914 
918 class PPDStackElement {
922  public $open;
923 
927  public $close;
928 
933  public $savedPrefix = '';
934 
938  public $count;
939 
943  public $parts;
944 
949  public $lineStart;
950 
951  public $partClass = PPDPart::class;
952 
953  public function __construct( $data = [] ) {
954  $class = $this->partClass;
955  $this->parts = [ new $class ];
956 
957  foreach ( $data as $name => $value ) {
958  $this->$name = $value;
959  }
960  }
961 
962  public function &getAccum() {
963  return $this->parts[count( $this->parts ) - 1]->out;
964  }
965 
966  public function addPart( $s = '' ) {
967  $class = $this->partClass;
968  $this->parts[] = new $class( $s );
969  }
970 
971  public function getCurrentPart() {
972  return $this->parts[count( $this->parts ) - 1];
973  }
974 
978  public function getFlags() {
979  $partCount = count( $this->parts );
980  $findPipe = $this->open != "\n" && $this->open != '[';
981  return [
982  'findPipe' => $findPipe,
983  'findEquals' => $findPipe && $partCount > 1 && !isset( $this->parts[$partCount - 1]->eqpos ),
984  'inHeading' => $this->open == "\n",
985  ];
986  }
987 
994  public function breakSyntax( $openingCount = false ) {
995  if ( $this->open == "\n" ) {
996  $s = $this->savedPrefix . $this->parts[0]->out;
997  } else {
998  if ( $openingCount === false ) {
999  $openingCount = $this->count;
1000  }
1001  $s = substr( $this->open, 0, -1 );
1002  $s .= str_repeat(
1003  substr( $this->open, -1 ),
1004  $openingCount - strlen( $s )
1005  );
1006  $s = $this->savedPrefix . $s;
1007  $first = true;
1008  foreach ( $this->parts as $part ) {
1009  if ( $first ) {
1010  $first = false;
1011  } else {
1012  $s .= '|';
1013  }
1014  $s .= $part->out;
1015  }
1016  }
1017  return $s;
1018  }
1019 }
1020 
1024 class PPDPart {
1028  public $out;
1029 
1030  // Optional member variables:
1031  // eqpos Position of equals sign in output accumulator
1032  // commentEnd Past-the-end input pointer for the last comment encountered
1033  // visualEnd Past-the-end input pointer for the end of the accumulator minus comments
1034 
1035  public function __construct( $out = '' ) {
1036  $this->out = $out;
1037  }
1038 }
1039 
1044 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
1045 class PPFrame_DOM implements PPFrame {
1046 
1050  public $preprocessor;
1051 
1055  public $parser;
1056 
1060  public $title;
1061  public $titleCache;
1062 
1067  public $loopCheckHash;
1068 
1073  public $depth;
1074 
1075  private $volatile = false;
1076  private $ttl = null;
1077 
1081  protected $childExpansionCache;
1082 
1087  public function __construct( $preprocessor ) {
1088  $this->preprocessor = $preprocessor;
1089  $this->parser = $preprocessor->parser;
1090  $this->title = $this->parser->mTitle;
1091  $this->titleCache = [ $this->title ? $this->title->getPrefixedDBkey() : false ];
1092  $this->loopCheckHash = [];
1093  $this->depth = 0;
1094  $this->childExpansionCache = [];
1095  }
1096 
1106  public function newChild( $args = false, $title = false, $indexOffset = 0 ) {
1107  $namedArgs = [];
1108  $numberedArgs = [];
1109  if ( $title === false ) {
1110  $title = $this->title;
1111  }
1112  if ( $args !== false ) {
1113  $xpath = false;
1114  if ( $args instanceof PPNode ) {
1115  $args = $args->node;
1116  }
1117  foreach ( $args as $arg ) {
1118  if ( $arg instanceof PPNode ) {
1119  $arg = $arg->node;
1120  }
1121  if ( !$xpath || $xpath->document !== $arg->ownerDocument ) {
1122  $xpath = new DOMXPath( $arg->ownerDocument );
1123  }
1124 
1125  $nameNodes = $xpath->query( 'name', $arg );
1126  $value = $xpath->query( 'value', $arg );
1127  if ( $nameNodes->item( 0 )->hasAttributes() ) {
1128  // Numbered parameter
1129  $index = $nameNodes->item( 0 )->attributes->getNamedItem( 'index' )->textContent;
1130  $index = $index - $indexOffset;
1131  if ( isset( $namedArgs[$index] ) || isset( $numberedArgs[$index] ) ) {
1132  $this->parser->getOutput()->addWarning( wfMessage( 'duplicate-args-warning',
1133  wfEscapeWikiText( $this->title ),
1134  wfEscapeWikiText( $title ),
1135  wfEscapeWikiText( $index ) )->text() );
1136  $this->parser->addTrackingCategory( 'duplicate-args-category' );
1137  }
1138  $numberedArgs[$index] = $value->item( 0 );
1139  unset( $namedArgs[$index] );
1140  } else {
1141  // Named parameter
1142  $name = trim( $this->expand( $nameNodes->item( 0 ), PPFrame::STRIP_COMMENTS ) );
1143  if ( isset( $namedArgs[$name] ) || isset( $numberedArgs[$name] ) ) {
1144  $this->parser->getOutput()->addWarning( wfMessage( 'duplicate-args-warning',
1145  wfEscapeWikiText( $this->title ),
1146  wfEscapeWikiText( $title ),
1147  wfEscapeWikiText( $name ) )->text() );
1148  $this->parser->addTrackingCategory( 'duplicate-args-category' );
1149  }
1150  $namedArgs[$name] = $value->item( 0 );
1151  unset( $numberedArgs[$name] );
1152  }
1153  }
1154  }
1155  return new PPTemplateFrame_DOM( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title );
1156  }
1157 
1165  public function cachedExpand( $key, $root, $flags = 0 ) {
1166  // we don't have a parent, so we don't have a cache
1167  return $this->expand( $root, $flags );
1168  }
1169 
1176  public function expand( $root, $flags = 0 ) {
1177  static $expansionDepth = 0;
1178  if ( is_string( $root ) ) {
1179  return $root;
1180  }
1181 
1182  if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount() ) {
1183  $this->parser->limitationWarn( 'node-count-exceeded',
1184  $this->parser->mPPNodeCount,
1185  $this->parser->mOptions->getMaxPPNodeCount()
1186  );
1187  return '<span class="error">Node-count limit exceeded</span>';
1188  }
1189 
1190  if ( $expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth() ) {
1191  $this->parser->limitationWarn( 'expansion-depth-exceeded',
1192  $expansionDepth,
1193  $this->parser->mOptions->getMaxPPExpandDepth()
1194  );
1195  return '<span class="error">Expansion depth limit exceeded</span>';
1196  }
1197  ++$expansionDepth;
1198  if ( $expansionDepth > $this->parser->mHighestExpansionDepth ) {
1199  $this->parser->mHighestExpansionDepth = $expansionDepth;
1200  }
1201 
1202  if ( $root instanceof PPNode_DOM ) {
1203  $root = $root->node;
1204  }
1205  if ( $root instanceof DOMDocument ) {
1206  $root = $root->documentElement;
1207  }
1208 
1209  $outStack = [ '', '' ];
1210  $iteratorStack = [ false, $root ];
1211  $indexStack = [ 0, 0 ];
1212 
1213  while ( count( $iteratorStack ) > 1 ) {
1214  $level = count( $outStack ) - 1;
1215  $iteratorNode =& $iteratorStack[$level];
1216  $out =& $outStack[$level];
1217  $index =& $indexStack[$level];
1218 
1219  if ( $iteratorNode instanceof PPNode_DOM ) {
1220  $iteratorNode = $iteratorNode->node;
1221  }
1222 
1223  if ( is_array( $iteratorNode ) ) {
1224  if ( $index >= count( $iteratorNode ) ) {
1225  // All done with this iterator
1226  $iteratorStack[$level] = false;
1227  $contextNode = false;
1228  } else {
1229  $contextNode = $iteratorNode[$index];
1230  $index++;
1231  }
1232  } elseif ( $iteratorNode instanceof DOMNodeList ) {
1233  if ( $index >= $iteratorNode->length ) {
1234  // All done with this iterator
1235  $iteratorStack[$level] = false;
1236  $contextNode = false;
1237  } else {
1238  $contextNode = $iteratorNode->item( $index );
1239  $index++;
1240  }
1241  } else {
1242  // Copy to $contextNode and then delete from iterator stack,
1243  // because this is not an iterator but we do have to execute it once
1244  $contextNode = $iteratorStack[$level];
1245  $iteratorStack[$level] = false;
1246  }
1247 
1248  if ( $contextNode instanceof PPNode_DOM ) {
1249  $contextNode = $contextNode->node;
1250  }
1251 
1252  $newIterator = false;
1253 
1254  if ( $contextNode === false ) {
1255  // nothing to do
1256  } elseif ( is_string( $contextNode ) ) {
1257  $out .= $contextNode;
1258  } elseif ( is_array( $contextNode ) || $contextNode instanceof DOMNodeList ) {
1259  $newIterator = $contextNode;
1260  } elseif ( $contextNode instanceof DOMNode ) {
1261  if ( $contextNode->nodeType == XML_TEXT_NODE ) {
1262  $out .= $contextNode->nodeValue;
1263  } elseif ( $contextNode->nodeName == 'template' ) {
1264  # Double-brace expansion
1265  $xpath = new DOMXPath( $contextNode->ownerDocument );
1266  $titles = $xpath->query( 'title', $contextNode );
1267  $title = $titles->item( 0 );
1268  $parts = $xpath->query( 'part', $contextNode );
1269  if ( $flags & PPFrame::NO_TEMPLATES ) {
1270  $newIterator = $this->virtualBracketedImplode( '{{', '|', '}}', $title, $parts );
1271  } else {
1272  $lineStart = $contextNode->getAttribute( 'lineStart' );
1273  $params = [
1274  'title' => new PPNode_DOM( $title ),
1275  'parts' => new PPNode_DOM( $parts ),
1276  'lineStart' => $lineStart ];
1277  $ret = $this->parser->braceSubstitution( $params, $this );
1278  if ( isset( $ret['object'] ) ) {
1279  $newIterator = $ret['object'];
1280  } else {
1281  $out .= $ret['text'];
1282  }
1283  }
1284  } elseif ( $contextNode->nodeName == 'tplarg' ) {
1285  # Triple-brace expansion
1286  $xpath = new DOMXPath( $contextNode->ownerDocument );
1287  $titles = $xpath->query( 'title', $contextNode );
1288  $title = $titles->item( 0 );
1289  $parts = $xpath->query( 'part', $contextNode );
1290  if ( $flags & PPFrame::NO_ARGS ) {
1291  $newIterator = $this->virtualBracketedImplode( '{{{', '|', '}}}', $title, $parts );
1292  } else {
1293  $params = [
1294  'title' => new PPNode_DOM( $title ),
1295  'parts' => new PPNode_DOM( $parts ) ];
1296  $ret = $this->parser->argSubstitution( $params, $this );
1297  if ( isset( $ret['object'] ) ) {
1298  $newIterator = $ret['object'];
1299  } else {
1300  $out .= $ret['text'];
1301  }
1302  }
1303  } elseif ( $contextNode->nodeName == 'comment' ) {
1304  # HTML-style comment
1305  # Remove it in HTML, pre+remove and STRIP_COMMENTS modes
1306  # Not in RECOVER_COMMENTS mode (msgnw) though.
1307  if ( ( $this->parser->ot['html']
1308  || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() )
1309  || ( $flags & PPFrame::STRIP_COMMENTS )
1310  ) && !( $flags & PPFrame::RECOVER_COMMENTS )
1311  ) {
1312  $out .= '';
1313  } elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) {
1314  # Add a strip marker in PST mode so that pstPass2() can
1315  # run some old-fashioned regexes on the result.
1316  # Not in RECOVER_COMMENTS mode (extractSections) though.
1317  $out .= $this->parser->insertStripItem( $contextNode->textContent );
1318  } else {
1319  # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
1320  $out .= $contextNode->textContent;
1321  }
1322  } elseif ( $contextNode->nodeName == 'ignore' ) {
1323  # Output suppression used by <includeonly> etc.
1324  # OT_WIKI will only respect <ignore> in substed templates.
1325  # The other output types respect it unless NO_IGNORE is set.
1326  # extractSections() sets NO_IGNORE and so never respects it.
1327  if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] )
1328  || ( $flags & PPFrame::NO_IGNORE )
1329  ) {
1330  $out .= $contextNode->textContent;
1331  } else {
1332  $out .= '';
1333  }
1334  } elseif ( $contextNode->nodeName == 'ext' ) {
1335  # Extension tag
1336  $xpath = new DOMXPath( $contextNode->ownerDocument );
1337  $names = $xpath->query( 'name', $contextNode );
1338  $attrs = $xpath->query( 'attr', $contextNode );
1339  $inners = $xpath->query( 'inner', $contextNode );
1340  $closes = $xpath->query( 'close', $contextNode );
1341  if ( $flags & PPFrame::NO_TAGS ) {
1342  $s = '<' . $this->expand( $names->item( 0 ), $flags );
1343  if ( $attrs->length > 0 ) {
1344  $s .= $this->expand( $attrs->item( 0 ), $flags );
1345  }
1346  if ( $inners->length > 0 ) {
1347  $s .= '>' . $this->expand( $inners->item( 0 ), $flags );
1348  if ( $closes->length > 0 ) {
1349  $s .= $this->expand( $closes->item( 0 ), $flags );
1350  }
1351  } else {
1352  $s .= '/>';
1353  }
1354  $out .= $s;
1355  } else {
1356  $params = [
1357  'name' => new PPNode_DOM( $names->item( 0 ) ),
1358  'attr' => $attrs->length > 0 ? new PPNode_DOM( $attrs->item( 0 ) ) : null,
1359  'inner' => $inners->length > 0 ? new PPNode_DOM( $inners->item( 0 ) ) : null,
1360  'close' => $closes->length > 0 ? new PPNode_DOM( $closes->item( 0 ) ) : null,
1361  ];
1362  $out .= $this->parser->extensionSubstitution( $params, $this );
1363  }
1364  } elseif ( $contextNode->nodeName == 'h' ) {
1365  # Heading
1366  $s = $this->expand( $contextNode->childNodes, $flags );
1367 
1368  # Insert a heading marker only for <h> children of <root>
1369  # This is to stop extractSections from going over multiple tree levels
1370  if ( $contextNode->parentNode->nodeName == 'root' && $this->parser->ot['html'] ) {
1371  # Insert heading index marker
1372  $headingIndex = $contextNode->getAttribute( 'i' );
1373  $titleText = $this->title->getPrefixedDBkey();
1374  $this->parser->mHeadings[] = [ $titleText, $headingIndex ];
1375  $serial = count( $this->parser->mHeadings ) - 1;
1376  $marker = Parser::MARKER_PREFIX . "-h-$serial-" . Parser::MARKER_SUFFIX;
1377  $count = $contextNode->getAttribute( 'level' );
1378  $s = substr( $s, 0, $count ) . $marker . substr( $s, $count );
1379  $this->parser->mStripState->addGeneral( $marker, '' );
1380  }
1381  $out .= $s;
1382  } else {
1383  # Generic recursive expansion
1384  $newIterator = $contextNode->childNodes;
1385  }
1386  } else {
1387  throw new MWException( __METHOD__ . ': Invalid parameter type' );
1388  }
1389 
1390  if ( $newIterator !== false ) {
1391  if ( $newIterator instanceof PPNode_DOM ) {
1392  $newIterator = $newIterator->node;
1393  }
1394  $outStack[] = '';
1395  $iteratorStack[] = $newIterator;
1396  $indexStack[] = 0;
1397  } elseif ( $iteratorStack[$level] === false ) {
1398  // Return accumulated value to parent
1399  // With tail recursion
1400  while ( $iteratorStack[$level] === false && $level > 0 ) {
1401  $outStack[$level - 1] .= $out;
1402  array_pop( $outStack );
1403  array_pop( $iteratorStack );
1404  array_pop( $indexStack );
1405  $level--;
1406  }
1407  }
1408  }
1409  --$expansionDepth;
1410  return $outStack[0];
1411  }
1412 
1419  public function implodeWithFlags( $sep, $flags /*, ... */ ) {
1420  $args = array_slice( func_get_args(), 2 );
1421 
1422  $first = true;
1423  $s = '';
1424  foreach ( $args as $root ) {
1425  if ( $root instanceof PPNode_DOM ) {
1426  $root = $root->node;
1427  }
1428  if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
1429  $root = [ $root ];
1430  }
1431  foreach ( $root as $node ) {
1432  if ( $first ) {
1433  $first = false;
1434  } else {
1435  $s .= $sep;
1436  }
1437  $s .= $this->expand( $node, $flags );
1438  }
1439  }
1440  return $s;
1441  }
1442 
1451  public function implode( $sep /*, ... */ ) {
1452  $args = array_slice( func_get_args(), 1 );
1453 
1454  $first = true;
1455  $s = '';
1456  foreach ( $args as $root ) {
1457  if ( $root instanceof PPNode_DOM ) {
1458  $root = $root->node;
1459  }
1460  if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
1461  $root = [ $root ];
1462  }
1463  foreach ( $root as $node ) {
1464  if ( $first ) {
1465  $first = false;
1466  } else {
1467  $s .= $sep;
1468  }
1469  $s .= $this->expand( $node );
1470  }
1471  }
1472  return $s;
1473  }
1474 
1483  public function virtualImplode( $sep /*, ... */ ) {
1484  $args = array_slice( func_get_args(), 1 );
1485  $out = [];
1486  $first = true;
1487 
1488  foreach ( $args as $root ) {
1489  if ( $root instanceof PPNode_DOM ) {
1490  $root = $root->node;
1491  }
1492  if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
1493  $root = [ $root ];
1494  }
1495  foreach ( $root as $node ) {
1496  if ( $first ) {
1497  $first = false;
1498  } else {
1499  $out[] = $sep;
1500  }
1501  $out[] = $node;
1502  }
1503  }
1504  return $out;
1505  }
1506 
1515  public function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) {
1516  $args = array_slice( func_get_args(), 3 );
1517  $out = [ $start ];
1518  $first = true;
1519 
1520  foreach ( $args as $root ) {
1521  if ( $root instanceof PPNode_DOM ) {
1522  $root = $root->node;
1523  }
1524  if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
1525  $root = [ $root ];
1526  }
1527  foreach ( $root as $node ) {
1528  if ( $first ) {
1529  $first = false;
1530  } else {
1531  $out[] = $sep;
1532  }
1533  $out[] = $node;
1534  }
1535  }
1536  $out[] = $end;
1537  return $out;
1538  }
1539 
1540  public function __toString() {
1541  return 'frame{}';
1542  }
1543 
1544  public function getPDBK( $level = false ) {
1545  if ( $level === false ) {
1546  return $this->title->getPrefixedDBkey();
1547  } else {
1548  return isset( $this->titleCache[$level] ) ? $this->titleCache[$level] : false;
1549  }
1550  }
1551 
1555  public function getArguments() {
1556  return [];
1557  }
1558 
1562  public function getNumberedArguments() {
1563  return [];
1564  }
1565 
1569  public function getNamedArguments() {
1570  return [];
1571  }
1572 
1578  public function isEmpty() {
1579  return true;
1580  }
1581 
1586  public function getArgument( $name ) {
1587  return false;
1588  }
1589 
1596  public function loopCheck( $title ) {
1597  return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] );
1598  }
1599 
1605  public function isTemplate() {
1606  return false;
1607  }
1608 
1614  public function getTitle() {
1615  return $this->title;
1616  }
1617 
1623  public function setVolatile( $flag = true ) {
1624  $this->volatile = $flag;
1625  }
1626 
1632  public function isVolatile() {
1633  return $this->volatile;
1634  }
1635 
1641  public function setTTL( $ttl ) {
1642  if ( $ttl !== null && ( $this->ttl === null || $ttl < $this->ttl ) ) {
1643  $this->ttl = $ttl;
1644  }
1645  }
1646 
1652  public function getTTL() {
1653  return $this->ttl;
1654  }
1655 }
1656 
1661 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
1662 class PPTemplateFrame_DOM extends PPFrame_DOM {
1663 
1664  public $numberedArgs, $namedArgs;
1665 
1669  public $parent;
1670  public $numberedExpansionCache, $namedExpansionCache;
1671 
1679  public function __construct( $preprocessor, $parent = false, $numberedArgs = [],
1680  $namedArgs = [], $title = false
1681  ) {
1682  parent::__construct( $preprocessor );
1683 
1684  $this->parent = $parent;
1685  $this->numberedArgs = $numberedArgs;
1686  $this->namedArgs = $namedArgs;
1687  $this->title = $title;
1688  $pdbk = $title ? $title->getPrefixedDBkey() : false;
1689  $this->titleCache = $parent->titleCache;
1690  $this->titleCache[] = $pdbk;
1691  $this->loopCheckHash = /*clone*/ $parent->loopCheckHash;
1692  if ( $pdbk !== false ) {
1693  $this->loopCheckHash[$pdbk] = true;
1694  }
1695  $this->depth = $parent->depth + 1;
1696  $this->numberedExpansionCache = $this->namedExpansionCache = [];
1697  }
1698 
1699  public function __toString() {
1700  $s = 'tplframe{';
1701  $first = true;
1702  $args = $this->numberedArgs + $this->namedArgs;
1703  foreach ( $args as $name => $value ) {
1704  if ( $first ) {
1705  $first = false;
1706  } else {
1707  $s .= ', ';
1708  }
1709  $s .= "\"$name\":\"" .
1710  str_replace( '"', '\\"', $value->ownerDocument->saveXML( $value ) ) . '"';
1711  }
1712  $s .= '}';
1713  return $s;
1714  }
1715 
1723  public function cachedExpand( $key, $root, $flags = 0 ) {
1724  if ( isset( $this->parent->childExpansionCache[$key] ) ) {
1725  return $this->parent->childExpansionCache[$key];
1726  }
1727  $retval = $this->expand( $root, $flags );
1728  if ( !$this->isVolatile() ) {
1729  $this->parent->childExpansionCache[$key] = $retval;
1730  }
1731  return $retval;
1732  }
1733 
1739  public function isEmpty() {
1740  return !count( $this->numberedArgs ) && !count( $this->namedArgs );
1741  }
1742 
1743  public function getArguments() {
1744  $arguments = [];
1745  foreach ( array_merge(
1746  array_keys( $this->numberedArgs ),
1747  array_keys( $this->namedArgs ) ) as $key ) {
1748  $arguments[$key] = $this->getArgument( $key );
1749  }
1750  return $arguments;
1751  }
1752 
1753  public function getNumberedArguments() {
1754  $arguments = [];
1755  foreach ( array_keys( $this->numberedArgs ) as $key ) {
1756  $arguments[$key] = $this->getArgument( $key );
1757  }
1758  return $arguments;
1759  }
1760 
1761  public function getNamedArguments() {
1762  $arguments = [];
1763  foreach ( array_keys( $this->namedArgs ) as $key ) {
1764  $arguments[$key] = $this->getArgument( $key );
1765  }
1766  return $arguments;
1767  }
1768 
1773  public function getNumberedArgument( $index ) {
1774  if ( !isset( $this->numberedArgs[$index] ) ) {
1775  return false;
1776  }
1777  if ( !isset( $this->numberedExpansionCache[$index] ) ) {
1778  # No trimming for unnamed arguments
1779  $this->numberedExpansionCache[$index] = $this->parent->expand(
1780  $this->numberedArgs[$index],
1781  PPFrame::STRIP_COMMENTS
1782  );
1783  }
1784  return $this->numberedExpansionCache[$index];
1785  }
1786 
1791  public function getNamedArgument( $name ) {
1792  if ( !isset( $this->namedArgs[$name] ) ) {
1793  return false;
1794  }
1795  if ( !isset( $this->namedExpansionCache[$name] ) ) {
1796  # Trim named arguments post-expand, for backwards compatibility
1797  $this->namedExpansionCache[$name] = trim(
1798  $this->parent->expand( $this->namedArgs[$name], PPFrame::STRIP_COMMENTS ) );
1799  }
1800  return $this->namedExpansionCache[$name];
1801  }
1802 
1807  public function getArgument( $name ) {
1808  $text = $this->getNumberedArgument( $name );
1809  if ( $text === false ) {
1810  $text = $this->getNamedArgument( $name );
1811  }
1812  return $text;
1813  }
1814 
1820  public function isTemplate() {
1821  return true;
1822  }
1823 
1824  public function setVolatile( $flag = true ) {
1825  parent::setVolatile( $flag );
1826  $this->parent->setVolatile( $flag );
1827  }
1828 
1829  public function setTTL( $ttl ) {
1830  parent::setTTL( $ttl );
1831  $this->parent->setTTL( $ttl );
1832  }
1833 }
1834 
1839 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
1840 class PPCustomFrame_DOM extends PPFrame_DOM {
1841 
1842  public $args;
1843 
1844  public function __construct( $preprocessor, $args ) {
1845  parent::__construct( $preprocessor );
1846  $this->args = $args;
1847  }
1848 
1849  public function __toString() {
1850  $s = 'cstmframe{';
1851  $first = true;
1852  foreach ( $this->args as $name => $value ) {
1853  if ( $first ) {
1854  $first = false;
1855  } else {
1856  $s .= ', ';
1857  }
1858  $s .= "\"$name\":\"" .
1859  str_replace( '"', '\\"', $value->__toString() ) . '"';
1860  }
1861  $s .= '}';
1862  return $s;
1863  }
1864 
1868  public function isEmpty() {
1869  return !count( $this->args );
1870  }
1871 
1876  public function getArgument( $index ) {
1877  if ( !isset( $this->args[$index] ) ) {
1878  return false;
1879  }
1880  return $this->args[$index];
1881  }
1882 
1883  public function getArguments() {
1884  return $this->args;
1885  }
1886 }
1887 
1891 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
1892 class PPNode_DOM implements PPNode {
1893 
1897  public $node;
1898  public $xpath;
1899 
1900  public function __construct( $node, $xpath = false ) {
1901  $this->node = $node;
1902  }
1903 
1907  public function getXPath() {
1908  if ( $this->xpath === null ) {
1909  $this->xpath = new DOMXPath( $this->node->ownerDocument );
1910  }
1911  return $this->xpath;
1912  }
1913 
1914  public function __toString() {
1915  if ( $this->node instanceof DOMNodeList ) {
1916  $s = '';
1917  foreach ( $this->node as $node ) {
1918  $s .= $node->ownerDocument->saveXML( $node );
1919  }
1920  } else {
1921  $s = $this->node->ownerDocument->saveXML( $this->node );
1922  }
1923  return $s;
1924  }
1925 
1929  public function getChildren() {
1930  return $this->node->childNodes ? new self( $this->node->childNodes ) : false;
1931  }
1932 
1936  public function getFirstChild() {
1937  return $this->node->firstChild ? new self( $this->node->firstChild ) : false;
1938  }
1939 
1943  public function getNextSibling() {
1944  return $this->node->nextSibling ? new self( $this->node->nextSibling ) : false;
1945  }
1946 
1952  public function getChildrenOfType( $type ) {
1953  return new self( $this->getXPath()->query( $type, $this->node ) );
1954  }
1955 
1959  public function getLength() {
1960  if ( $this->node instanceof DOMNodeList ) {
1961  return $this->node->length;
1962  } else {
1963  return false;
1964  }
1965  }
1966 
1971  public function item( $i ) {
1972  $item = $this->node->item( $i );
1973  return $item ? new self( $item ) : false;
1974  }
1975 
1979  public function getName() {
1980  if ( $this->node instanceof DOMNodeList ) {
1981  return '#nodelist';
1982  } else {
1983  return $this->node->nodeName;
1984  }
1985  }
1986 
1996  public function splitArg() {
1997  $xpath = $this->getXPath();
1998  $names = $xpath->query( 'name', $this->node );
1999  $values = $xpath->query( 'value', $this->node );
2000  if ( !$names->length || !$values->length ) {
2001  throw new MWException( 'Invalid brace node passed to ' . __METHOD__ );
2002  }
2003  $name = $names->item( 0 );
2004  $index = $name->getAttribute( 'index' );
2005  return [
2006  'name' => new self( $name ),
2007  'index' => $index,
2008  'value' => new self( $values->item( 0 ) ) ];
2009  }
2010 
2018  public function splitExt() {
2019  $xpath = $this->getXPath();
2020  $names = $xpath->query( 'name', $this->node );
2021  $attrs = $xpath->query( 'attr', $this->node );
2022  $inners = $xpath->query( 'inner', $this->node );
2023  $closes = $xpath->query( 'close', $this->node );
2024  if ( !$names->length || !$attrs->length ) {
2025  throw new MWException( 'Invalid ext node passed to ' . __METHOD__ );
2026  }
2027  $parts = [
2028  'name' => new self( $names->item( 0 ) ),
2029  'attr' => new self( $attrs->item( 0 ) ) ];
2030  if ( $inners->length ) {
2031  $parts['inner'] = new self( $inners->item( 0 ) );
2032  }
2033  if ( $closes->length ) {
2034  $parts['close'] = new self( $closes->item( 0 ) );
2035  }
2036  return $parts;
2037  }
2038 
2044  public function splitHeading() {
2045  if ( $this->getName() !== 'h' ) {
2046  throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
2047  }
2048  return [
2049  'i' => $this->node->getAttribute( 'i' ),
2050  'level' => $this->node->getAttribute( 'level' ),
2051  'contents' => $this->getChildren()
2052  ];
2053  }
2054 }
Preprocessor_DOM\preprocessToXml
preprocessToXml( $text, $flags=0)
Definition: Preprocessor_DOM.php:193
Preprocessor_DOM
Definition: Preprocessor_DOM.php:28
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 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:30
PPDPart
Definition: Preprocessor_DOM.php:1024
PPNode_DOM
Definition: Preprocessor_DOM.php:1892
PPNode_DOM\item
item( $i)
Definition: Preprocessor_DOM.php:1971
PPNode_DOM\$xpath
$xpath
Definition: Preprocessor_DOM.php:1898
Preprocessor_DOM\$memoryLimit
$memoryLimit
Definition: Preprocessor_DOM.php:35
Preprocessor_DOM\$parser
Parser $parser
Definition: Preprocessor_DOM.php:33
PPNode_DOM\$node
DOMElement $node
Definition: Preprocessor_DOM.php:1897
names
within a display generated by the Derivative if and wherever such third party notices normally appear The contents of the NOTICE file are for informational purposes only and do not modify the License You may add Your own attribution notices within Derivative Works that You alongside or as an addendum to the NOTICE text from the provided that such additional attribution notices cannot be construed as modifying the License You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for or distribution of Your or for any such Derivative Works as a provided Your and distribution of the Work otherwise complies with the conditions stated in this License Submission of Contributions Unless You explicitly state any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this without any additional terms or conditions Notwithstanding the nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions Trademarks This License does not grant permission to use the trade names
Definition: APACHE-LICENSE-2.0.txt:140
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:18
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:27
$s
$s
Definition: mergeMessageFileList.php:187
PPNode_DOM\splitHeading
splitHeading()
Split a "<h>" node.
Definition: Preprocessor_DOM.php:2044
PPNode_DOM\getXPath
getXPath()
Definition: Preprocessor_DOM.php:1907
$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. 'ImgAuthModifyHeaders':Executed just before a file is streamed to a user via img_auth.php, allowing headers to be modified beforehand. $title:LinkTarget object & $headers:HTTP headers(name=> value, names are case insensitive). Two headers get special handling:If-Modified-Since(value must be a valid HTTP date) and Range(must be of the form "bytes=(\d*-\d*)") will be honored when streaming the file. '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 'ImportHandleUnknownUser':When a user doesn 't exist locally, this hook is called to give extensions an opportunity to auto-create it. If the auto-creation is successful, return false. $name:User name '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:1993
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:37
part
in this case you re responsible for computing and outputting the entire conflict part
Definition: hooks.txt:1421
Preprocessor
Definition: Preprocessor.php:29
PPNode_DOM\__construct
__construct( $node, $xpath=false)
Definition: Preprocessor_DOM.php:1900
PPCustomFrame_DOM
Expansion frame with custom arguments.
Definition: Preprocessor_DOM.php:1840
PPNode_DOM\getName
getName()
Definition: Preprocessor_DOM.php:1979
MWException
MediaWiki exception.
Definition: MWException.php:26
PPNode_DOM\getLength
getLength()
Definition: Preprocessor_DOM.php:1959
PPCustomFrame_DOM\getArgument
getArgument( $index)
Definition: Preprocessor_DOM.php:1876
$matches
$matches
Definition: NoLocalSettings.php:24
Preprocessor_DOM\__construct
__construct( $parser)
Definition: Preprocessor_DOM.php:39
PPNode
There are three types of nodes:
Definition: Preprocessor.php:359
PPNode_DOM\getChildren
getChildren()
Definition: Preprocessor_DOM.php:1929
title
title
Definition: parserTests.txt:219
Preprocessor_DOM\newCustomFrame
newCustomFrame( $args)
Definition: Preprocessor_DOM.php:63
Parser\PTD_FOR_INCLUSION
const PTD_FOR_INCLUSION
Definition: Parser.php:107
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:95
PPNode_DOM\getChildrenOfType
getChildrenOfType( $type)
Definition: Preprocessor_DOM.php:1952
$wgDisableLangConversion
$wgDisableLangConversion
Whether to enable language variant conversion.
Definition: DefaultSettings.php:3058
Preprocessor_DOM\memCheck
memCheck()
Definition: Preprocessor_DOM.php:113
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:1045
PPDStack
Stack class to help Preprocessor::preprocessToObj()
Definition: Preprocessor_DOM.php:829
so
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 and or sell copies of the and to permit persons to whom the Software is furnished to do so
Definition: LICENSE.txt:13
PPNode_DOM\__toString
__toString()
Definition: Preprocessor_DOM.php:1914
Parser
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition: Parser.php:70
$args
if( $line===false) $args
Definition: cdb.php:64
Preprocessor\cacheSetTree
cacheSetTree( $text, $flags, $tree)
Store a document tree in the cache.
Definition: Preprocessor.php:67
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
Preprocessor_DOM\CACHE_PREFIX
const CACHE_PREFIX
Definition: Preprocessor_DOM.php:37
PPCustomFrame_DOM\isEmpty
isEmpty()
Definition: Preprocessor_DOM.php:1868
PPNode_DOM\splitArg
splitArg()
Split a "<part>" node into an associative array containing:
Definition: Preprocessor_DOM.php:1996
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:22
Preprocessor_DOM\newPartNodeArray
newPartNodeArray( $values)
Definition: Preprocessor_DOM.php:72
Preprocessor_DOM\newFrame
newFrame()
Definition: Preprocessor_DOM.php:55
PPNode_DOM\getFirstChild
getFirstChild()
Definition: Preprocessor_DOM.php:1936
PPCustomFrame_DOM\getArguments
getArguments()
Definition: Preprocessor_DOM.php:1883
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
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
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:2018
Preprocessor_DOM\preprocessToObj
preprocessToObj( $text, $flags=0)
Preprocess some wikitext and return the document tree.
Definition: Preprocessor_DOM.php:149
PPNode_DOM\getNextSibling
getNextSibling()
Definition: Preprocessor_DOM.php:1943
$type
$type
Definition: testCompression.php:48