MediaWiki  1.33.0
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  // FIXME: Don't use assert()
570  // phpcs:ignore MediaWiki.Usage.ForbiddenFunctions.assert
571  assert( $piece->open === "\n" );
572  $part = $piece->getCurrentPart();
573  // Search back through the input to see if it has a proper close.
574  // Do this using the reversed string since the other solutions
575  // (end anchor, etc.) are inefficient.
576  $wsLength = strspn( $revText, " \t", $lengthText - $i );
577  $searchStart = $i - $wsLength;
578  if ( isset( $part->commentEnd ) && $searchStart - 1 == $part->commentEnd ) {
579  // Comment found at line end
580  // Search for equals signs before the comment
581  $searchStart = $part->visualEnd;
582  $searchStart -= strspn( $revText, " \t", $lengthText - $searchStart );
583  }
584  $count = $piece->count;
585  $equalsLength = strspn( $revText, '=', $lengthText - $searchStart );
586  if ( $equalsLength > 0 ) {
587  if ( $searchStart - $equalsLength == $piece->startPos ) {
588  // This is just a single string of equals signs on its own line
589  // Replicate the doHeadings behavior /={count}(.+)={count}/
590  // First find out how many equals signs there really are (don't stop at 6)
591  $count = $equalsLength;
592  if ( $count < 3 ) {
593  $count = 0;
594  } else {
595  $count = min( 6, intval( ( $count - 1 ) / 2 ) );
596  }
597  } else {
598  $count = min( $equalsLength, $count );
599  }
600  if ( $count > 0 ) {
601  // Normal match, output <h>
602  $element = "<h level=\"$count\" i=\"$headingIndex\">$accum</h>";
603  $headingIndex++;
604  } else {
605  // Single equals sign on its own line, count=0
606  $element = $accum;
607  }
608  } else {
609  // No match, no <h>, just pass down the inner text
610  $element = $accum;
611  }
612  // Unwind the stack
613  $stack->pop();
614  $accum =& $stack->getAccum();
615  $stackFlags = $stack->getFlags();
616  if ( isset( $stackFlags['findEquals'] ) ) {
617  $findEquals = $stackFlags['findEquals'];
618  }
619  if ( isset( $stackFlags['findPipe'] ) ) {
620  $findPipe = $stackFlags['findPipe'];
621  }
622  if ( isset( $stackFlags['inHeading'] ) ) {
623  $inHeading = $stackFlags['inHeading'];
624  }
625 
626  // Append the result to the enclosing accumulator
627  $accum .= $element;
628  // Note that we do NOT increment the input pointer.
629  // This is because the closing linebreak could be the opening linebreak of
630  // another heading. Infinite loops are avoided because the next iteration MUST
631  // hit the heading open case above, which unconditionally increments the
632  // input pointer.
633  } elseif ( $found == 'open' ) {
634  # count opening brace characters
635  $curLen = strlen( $curChar );
636  $count = ( $curLen > 1 ) ?
637  # allow the final character to repeat
638  strspn( $text, $curChar[$curLen - 1], $i + 1 ) + 1 :
639  strspn( $text, $curChar, $i );
640 
641  $savedPrefix = '';
642  $lineStart = ( $i > 0 && $text[$i - 1] == "\n" );
643 
644  if ( $curChar === "-{" && $count > $curLen ) {
645  // -{ => {{ transition because rightmost wins
646  $savedPrefix = '-';
647  $i++;
648  $curChar = '{';
649  $count--;
650  $rule = $this->rules[$curChar];
651  }
652 
653  # we need to add to stack only if opening brace count is enough for one of the rules
654  if ( $count >= $rule['min'] ) {
655  # Add it to the stack
656  $piece = [
657  'open' => $curChar,
658  'close' => $rule['end'],
659  'savedPrefix' => $savedPrefix,
660  'count' => $count,
661  'lineStart' => $lineStart,
662  ];
663 
664  $stack->push( $piece );
665  $accum =& $stack->getAccum();
666  $stackFlags = $stack->getFlags();
667  if ( isset( $stackFlags['findEquals'] ) ) {
668  $findEquals = $stackFlags['findEquals'];
669  }
670  if ( isset( $stackFlags['findPipe'] ) ) {
671  $findPipe = $stackFlags['findPipe'];
672  }
673  if ( isset( $stackFlags['inHeading'] ) ) {
674  $inHeading = $stackFlags['inHeading'];
675  }
676  } else {
677  # Add literal brace(s)
678  $accum .= htmlspecialchars( $savedPrefix . str_repeat( $curChar, $count ) );
679  }
680  $i += $count;
681  } elseif ( $found == 'close' ) {
682  $piece = $stack->top;
683  # lets check if there are enough characters for closing brace
684  $maxCount = $piece->count;
685  if ( $piece->close === '}-' && $curChar === '}' ) {
686  $maxCount--; # don't try to match closing '-' as a '}'
687  }
688  $curLen = strlen( $curChar );
689  $count = ( $curLen > 1 ) ? $curLen :
690  strspn( $text, $curChar, $i, $maxCount );
691 
692  # check for maximum matching characters (if there are 5 closing
693  # characters, we will probably need only 3 - depending on the rules)
694  $rule = $this->rules[$piece->open];
695  if ( $count > $rule['max'] ) {
696  # The specified maximum exists in the callback array, unless the caller
697  # has made an error
698  $matchingCount = $rule['max'];
699  } else {
700  # Count is less than the maximum
701  # Skip any gaps in the callback array to find the true largest match
702  # Need to use array_key_exists not isset because the callback can be null
703  $matchingCount = $count;
704  while ( $matchingCount > 0 && !array_key_exists( $matchingCount, $rule['names'] ) ) {
705  --$matchingCount;
706  }
707  }
708 
709  if ( $matchingCount <= 0 ) {
710  # No matching element found in callback array
711  # Output a literal closing brace and continue
712  $endText = substr( $text, $i, $count );
713  $accum .= htmlspecialchars( $endText );
714  $i += $count;
715  continue;
716  }
717  $name = $rule['names'][$matchingCount];
718  if ( $name === null ) {
719  // No element, just literal text
720  $endText = substr( $text, $i, $matchingCount );
721  $element = $piece->breakSyntax( $matchingCount ) . $endText;
722  } else {
723  # Create XML element
724  # Note: $parts is already XML, does not need to be encoded further
725  $parts = $piece->parts;
726  $title = $parts[0]->out;
727  unset( $parts[0] );
728 
729  # The invocation is at the start of the line if lineStart is set in
730  # the stack, and all opening brackets are used up.
731  if ( $maxCount == $matchingCount &&
732  !empty( $piece->lineStart ) &&
733  strlen( $piece->savedPrefix ) == 0 ) {
734  $attr = ' lineStart="1"';
735  } else {
736  $attr = '';
737  }
738 
739  $element = "<$name$attr>";
740  $element .= "<title>$title</title>";
741  $argIndex = 1;
742  foreach ( $parts as $part ) {
743  if ( isset( $part->eqpos ) ) {
744  $argName = substr( $part->out, 0, $part->eqpos );
745  $argValue = substr( $part->out, $part->eqpos + 1 );
746  $element .= "<part><name>$argName</name>=<value>$argValue</value></part>";
747  } else {
748  $element .= "<part><name index=\"$argIndex\" /><value>{$part->out}</value></part>";
749  $argIndex++;
750  }
751  }
752  $element .= "</$name>";
753  }
754 
755  # Advance input pointer
756  $i += $matchingCount;
757 
758  # Unwind the stack
759  $stack->pop();
760  $accum =& $stack->getAccum();
761 
762  # Re-add the old stack element if it still has unmatched opening characters remaining
763  if ( $matchingCount < $piece->count ) {
764  $piece->parts = [ new PPDPart ];
765  $piece->count -= $matchingCount;
766  # do we still qualify for any callback with remaining count?
767  $min = $this->rules[$piece->open]['min'];
768  if ( $piece->count >= $min ) {
769  $stack->push( $piece );
770  $accum =& $stack->getAccum();
771  } elseif ( $piece->count == 1 && $piece->open === '{' && $piece->savedPrefix === '-' ) {
772  $piece->savedPrefix = '';
773  $piece->open = '-{';
774  $piece->count = 2;
775  $piece->close = $this->rules[$piece->open]['end'];
776  $stack->push( $piece );
777  $accum =& $stack->getAccum();
778  } else {
779  $s = substr( $piece->open, 0, -1 );
780  $s .= str_repeat(
781  substr( $piece->open, -1 ),
782  $piece->count - strlen( $s )
783  );
784  $accum .= $piece->savedPrefix . $s;
785  }
786  } elseif ( $piece->savedPrefix !== '' ) {
787  $accum .= $piece->savedPrefix;
788  }
789 
790  $stackFlags = $stack->getFlags();
791  if ( isset( $stackFlags['findEquals'] ) ) {
792  $findEquals = $stackFlags['findEquals'];
793  }
794  if ( isset( $stackFlags['findPipe'] ) ) {
795  $findPipe = $stackFlags['findPipe'];
796  }
797  if ( isset( $stackFlags['inHeading'] ) ) {
798  $inHeading = $stackFlags['inHeading'];
799  }
800 
801  # Add XML element to the enclosing accumulator
802  $accum .= $element;
803  } elseif ( $found == 'pipe' ) {
804  $findEquals = true; // shortcut for getFlags()
805  $stack->addPart();
806  $accum =& $stack->getAccum();
807  ++$i;
808  } elseif ( $found == 'equals' ) {
809  $findEquals = false; // shortcut for getFlags()
810  $stack->getCurrentPart()->eqpos = strlen( $accum );
811  $accum .= '=';
812  ++$i;
813  }
814  }
815 
816  # Output any remaining unclosed brackets
817  foreach ( $stack->stack as $piece ) {
818  $stack->rootAccum .= $piece->breakSyntax();
819  }
820  $stack->rootAccum .= '</root>';
821  $xml = $stack->rootAccum;
822 
823  return $xml;
824  }
825 }
826 
831 class PPDStack {
832  public $stack, $rootAccum;
833 
837  public $top;
838  public $out;
839  public $elementClass = PPDStackElement::class;
840 
841  public static $false = false;
842 
843  public function __construct() {
844  $this->stack = [];
845  $this->top = false;
846  $this->rootAccum = '';
847  $this->accum =& $this->rootAccum;
848  }
849 
853  public function count() {
854  return count( $this->stack );
855  }
856 
857  public function &getAccum() {
858  return $this->accum;
859  }
860 
864  public function getCurrentPart() {
865  if ( $this->top === false ) {
866  return false;
867  } else {
868  return $this->top->getCurrentPart();
869  }
870  }
871 
872  public function push( $data ) {
873  if ( $data instanceof $this->elementClass ) {
874  $this->stack[] = $data;
875  } else {
876  $class = $this->elementClass;
877  $this->stack[] = new $class( $data );
878  }
879  $this->top = $this->stack[count( $this->stack ) - 1];
880  $this->accum =& $this->top->getAccum();
881  }
882 
883  public function pop() {
884  if ( $this->stack === [] ) {
885  throw new MWException( __METHOD__ . ': no elements remaining' );
886  }
887  $temp = array_pop( $this->stack );
888 
889  if ( count( $this->stack ) ) {
890  $this->top = $this->stack[count( $this->stack ) - 1];
891  $this->accum =& $this->top->getAccum();
892  } else {
893  $this->top = self::$false;
894  $this->accum =& $this->rootAccum;
895  }
896  return $temp;
897  }
898 
899  public function addPart( $s = '' ) {
900  $this->top->addPart( $s );
901  $this->accum =& $this->top->getAccum();
902  }
903 
907  public function getFlags() {
908  if ( $this->stack === [] ) {
909  return [
910  'findEquals' => false,
911  'findPipe' => false,
912  'inHeading' => false,
913  ];
914  } else {
915  return $this->top->getFlags();
916  }
917  }
918 }
919 
923 class PPDStackElement {
927  public $open;
928 
932  public $close;
933 
938  public $savedPrefix = '';
939 
943  public $count;
944 
948  public $parts;
949 
954  public $lineStart;
955 
956  public $partClass = PPDPart::class;
957 
958  public function __construct( $data = [] ) {
959  $class = $this->partClass;
960  $this->parts = [ new $class ];
961 
962  foreach ( $data as $name => $value ) {
963  $this->$name = $value;
964  }
965  }
966 
967  public function &getAccum() {
968  return $this->parts[count( $this->parts ) - 1]->out;
969  }
970 
971  public function addPart( $s = '' ) {
972  $class = $this->partClass;
973  $this->parts[] = new $class( $s );
974  }
975 
979  public function getCurrentPart() {
980  return $this->parts[count( $this->parts ) - 1];
981  }
982 
986  public function getFlags() {
987  $partCount = count( $this->parts );
988  $findPipe = $this->open != "\n" && $this->open != '[';
989  return [
990  'findPipe' => $findPipe,
991  'findEquals' => $findPipe && $partCount > 1 && !isset( $this->parts[$partCount - 1]->eqpos ),
992  'inHeading' => $this->open == "\n",
993  ];
994  }
995 
1002  public function breakSyntax( $openingCount = false ) {
1003  if ( $this->open == "\n" ) {
1004  $s = $this->savedPrefix . $this->parts[0]->out;
1005  } else {
1006  if ( $openingCount === false ) {
1007  $openingCount = $this->count;
1008  }
1009  $s = substr( $this->open, 0, -1 );
1010  $s .= str_repeat(
1011  substr( $this->open, -1 ),
1012  $openingCount - strlen( $s )
1013  );
1014  $s = $this->savedPrefix . $s;
1015  $first = true;
1016  foreach ( $this->parts as $part ) {
1017  if ( $first ) {
1018  $first = false;
1019  } else {
1020  $s .= '|';
1021  }
1022  $s .= $part->out;
1023  }
1024  }
1025  return $s;
1026  }
1027 }
1028 
1032 class PPDPart {
1036  public $out;
1037 
1038  // Optional member variables:
1039  // eqpos Position of equals sign in output accumulator
1040  // commentEnd Past-the-end input pointer for the last comment encountered
1041  // visualEnd Past-the-end input pointer for the end of the accumulator minus comments
1042 
1043  public function __construct( $out = '' ) {
1044  $this->out = $out;
1045  }
1046 }
1047 
1052 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
1053 class PPFrame_DOM implements PPFrame {
1054 
1058  public $preprocessor;
1059 
1063  public $parser;
1064 
1068  public $title;
1069  public $titleCache;
1070 
1075  public $loopCheckHash;
1076 
1081  public $depth;
1082 
1083  private $volatile = false;
1084  private $ttl = null;
1085 
1089  protected $childExpansionCache;
1090 
1095  public function __construct( $preprocessor ) {
1096  $this->preprocessor = $preprocessor;
1097  $this->parser = $preprocessor->parser;
1098  $this->title = $this->parser->mTitle;
1099  $this->titleCache = [ $this->title ? $this->title->getPrefixedDBkey() : false ];
1100  $this->loopCheckHash = [];
1101  $this->depth = 0;
1102  $this->childExpansionCache = [];
1103  }
1104 
1114  public function newChild( $args = false, $title = false, $indexOffset = 0 ) {
1115  $namedArgs = [];
1116  $numberedArgs = [];
1117  if ( $title === false ) {
1118  $title = $this->title;
1119  }
1120  if ( $args !== false ) {
1121  $xpath = false;
1122  if ( $args instanceof PPNode ) {
1123  $args = $args->node;
1124  }
1125  foreach ( $args as $arg ) {
1126  if ( $arg instanceof PPNode ) {
1127  $arg = $arg->node;
1128  }
1129  if ( !$xpath || $xpath->document !== $arg->ownerDocument ) {
1130  $xpath = new DOMXPath( $arg->ownerDocument );
1131  }
1132 
1133  $nameNodes = $xpath->query( 'name', $arg );
1134  $value = $xpath->query( 'value', $arg );
1135  if ( $nameNodes->item( 0 )->hasAttributes() ) {
1136  // Numbered parameter
1137  $index = $nameNodes->item( 0 )->attributes->getNamedItem( 'index' )->textContent;
1138  $index = $index - $indexOffset;
1139  if ( isset( $namedArgs[$index] ) || isset( $numberedArgs[$index] ) ) {
1140  $this->parser->getOutput()->addWarning( wfMessage( 'duplicate-args-warning',
1141  wfEscapeWikiText( $this->title ),
1142  wfEscapeWikiText( $title ),
1143  wfEscapeWikiText( $index ) )->text() );
1144  $this->parser->addTrackingCategory( 'duplicate-args-category' );
1145  }
1146  $numberedArgs[$index] = $value->item( 0 );
1147  unset( $namedArgs[$index] );
1148  } else {
1149  // Named parameter
1150  $name = trim( $this->expand( $nameNodes->item( 0 ), PPFrame::STRIP_COMMENTS ) );
1151  if ( isset( $namedArgs[$name] ) || isset( $numberedArgs[$name] ) ) {
1152  $this->parser->getOutput()->addWarning( wfMessage( 'duplicate-args-warning',
1153  wfEscapeWikiText( $this->title ),
1154  wfEscapeWikiText( $title ),
1155  wfEscapeWikiText( $name ) )->text() );
1156  $this->parser->addTrackingCategory( 'duplicate-args-category' );
1157  }
1158  $namedArgs[$name] = $value->item( 0 );
1159  unset( $numberedArgs[$name] );
1160  }
1161  }
1162  }
1163  return new PPTemplateFrame_DOM( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title );
1164  }
1165 
1173  public function cachedExpand( $key, $root, $flags = 0 ) {
1174  // we don't have a parent, so we don't have a cache
1175  return $this->expand( $root, $flags );
1176  }
1177 
1184  public function expand( $root, $flags = 0 ) {
1185  static $expansionDepth = 0;
1186  if ( is_string( $root ) ) {
1187  return $root;
1188  }
1189 
1190  if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount() ) {
1191  $this->parser->limitationWarn( 'node-count-exceeded',
1192  $this->parser->mPPNodeCount,
1193  $this->parser->mOptions->getMaxPPNodeCount()
1194  );
1195  return '<span class="error">Node-count limit exceeded</span>';
1196  }
1197 
1198  if ( $expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth() ) {
1199  $this->parser->limitationWarn( 'expansion-depth-exceeded',
1200  $expansionDepth,
1201  $this->parser->mOptions->getMaxPPExpandDepth()
1202  );
1203  return '<span class="error">Expansion depth limit exceeded</span>';
1204  }
1205  ++$expansionDepth;
1206  if ( $expansionDepth > $this->parser->mHighestExpansionDepth ) {
1207  $this->parser->mHighestExpansionDepth = $expansionDepth;
1208  }
1209 
1210  if ( $root instanceof PPNode_DOM ) {
1211  $root = $root->node;
1212  }
1213  if ( $root instanceof DOMDocument ) {
1214  $root = $root->documentElement;
1215  }
1216 
1217  $outStack = [ '', '' ];
1218  $iteratorStack = [ false, $root ];
1219  $indexStack = [ 0, 0 ];
1220 
1221  while ( count( $iteratorStack ) > 1 ) {
1222  $level = count( $outStack ) - 1;
1223  $iteratorNode =& $iteratorStack[$level];
1224  $out =& $outStack[$level];
1225  $index =& $indexStack[$level];
1226 
1227  if ( $iteratorNode instanceof PPNode_DOM ) {
1228  $iteratorNode = $iteratorNode->node;
1229  }
1230 
1231  if ( is_array( $iteratorNode ) ) {
1232  if ( $index >= count( $iteratorNode ) ) {
1233  // All done with this iterator
1234  $iteratorStack[$level] = false;
1235  $contextNode = false;
1236  } else {
1237  $contextNode = $iteratorNode[$index];
1238  $index++;
1239  }
1240  } elseif ( $iteratorNode instanceof DOMNodeList ) {
1241  if ( $index >= $iteratorNode->length ) {
1242  // All done with this iterator
1243  $iteratorStack[$level] = false;
1244  $contextNode = false;
1245  } else {
1246  $contextNode = $iteratorNode->item( $index );
1247  $index++;
1248  }
1249  } else {
1250  // Copy to $contextNode and then delete from iterator stack,
1251  // because this is not an iterator but we do have to execute it once
1252  $contextNode = $iteratorStack[$level];
1253  $iteratorStack[$level] = false;
1254  }
1255 
1256  if ( $contextNode instanceof PPNode_DOM ) {
1257  $contextNode = $contextNode->node;
1258  }
1259 
1260  $newIterator = false;
1261 
1262  if ( $contextNode === false ) {
1263  // nothing to do
1264  } elseif ( is_string( $contextNode ) ) {
1265  $out .= $contextNode;
1266  } elseif ( is_array( $contextNode ) || $contextNode instanceof DOMNodeList ) {
1267  $newIterator = $contextNode;
1268  } elseif ( $contextNode instanceof DOMNode ) {
1269  if ( $contextNode->nodeType == XML_TEXT_NODE ) {
1270  $out .= $contextNode->nodeValue;
1271  } elseif ( $contextNode->nodeName == 'template' ) {
1272  # Double-brace expansion
1273  $xpath = new DOMXPath( $contextNode->ownerDocument );
1274  $titles = $xpath->query( 'title', $contextNode );
1275  $title = $titles->item( 0 );
1276  $parts = $xpath->query( 'part', $contextNode );
1277  if ( $flags & PPFrame::NO_TEMPLATES ) {
1278  $newIterator = $this->virtualBracketedImplode( '{{', '|', '}}', $title, $parts );
1279  } else {
1280  $lineStart = $contextNode->getAttribute( 'lineStart' );
1281  $params = [
1282  'title' => new PPNode_DOM( $title ),
1283  'parts' => new PPNode_DOM( $parts ),
1284  'lineStart' => $lineStart ];
1285  $ret = $this->parser->braceSubstitution( $params, $this );
1286  if ( isset( $ret['object'] ) ) {
1287  $newIterator = $ret['object'];
1288  } else {
1289  $out .= $ret['text'];
1290  }
1291  }
1292  } elseif ( $contextNode->nodeName == 'tplarg' ) {
1293  # Triple-brace expansion
1294  $xpath = new DOMXPath( $contextNode->ownerDocument );
1295  $titles = $xpath->query( 'title', $contextNode );
1296  $title = $titles->item( 0 );
1297  $parts = $xpath->query( 'part', $contextNode );
1298  if ( $flags & PPFrame::NO_ARGS ) {
1299  $newIterator = $this->virtualBracketedImplode( '{{{', '|', '}}}', $title, $parts );
1300  } else {
1301  $params = [
1302  'title' => new PPNode_DOM( $title ),
1303  'parts' => new PPNode_DOM( $parts ) ];
1304  $ret = $this->parser->argSubstitution( $params, $this );
1305  if ( isset( $ret['object'] ) ) {
1306  $newIterator = $ret['object'];
1307  } else {
1308  $out .= $ret['text'];
1309  }
1310  }
1311  } elseif ( $contextNode->nodeName == 'comment' ) {
1312  # HTML-style comment
1313  # Remove it in HTML, pre+remove and STRIP_COMMENTS modes
1314  # Not in RECOVER_COMMENTS mode (msgnw) though.
1315  if ( ( $this->parser->ot['html']
1316  || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() )
1317  || ( $flags & PPFrame::STRIP_COMMENTS )
1318  ) && !( $flags & PPFrame::RECOVER_COMMENTS )
1319  ) {
1320  $out .= '';
1321  } elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) {
1322  # Add a strip marker in PST mode so that pstPass2() can
1323  # run some old-fashioned regexes on the result.
1324  # Not in RECOVER_COMMENTS mode (extractSections) though.
1325  $out .= $this->parser->insertStripItem( $contextNode->textContent );
1326  } else {
1327  # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
1328  $out .= $contextNode->textContent;
1329  }
1330  } elseif ( $contextNode->nodeName == 'ignore' ) {
1331  # Output suppression used by <includeonly> etc.
1332  # OT_WIKI will only respect <ignore> in substed templates.
1333  # The other output types respect it unless NO_IGNORE is set.
1334  # extractSections() sets NO_IGNORE and so never respects it.
1335  if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] )
1336  || ( $flags & PPFrame::NO_IGNORE )
1337  ) {
1338  $out .= $contextNode->textContent;
1339  } else {
1340  $out .= '';
1341  }
1342  } elseif ( $contextNode->nodeName == 'ext' ) {
1343  # Extension tag
1344  $xpath = new DOMXPath( $contextNode->ownerDocument );
1345  $names = $xpath->query( 'name', $contextNode );
1346  $attrs = $xpath->query( 'attr', $contextNode );
1347  $inners = $xpath->query( 'inner', $contextNode );
1348  $closes = $xpath->query( 'close', $contextNode );
1349  if ( $flags & PPFrame::NO_TAGS ) {
1350  $s = '<' . $this->expand( $names->item( 0 ), $flags );
1351  if ( $attrs->length > 0 ) {
1352  $s .= $this->expand( $attrs->item( 0 ), $flags );
1353  }
1354  if ( $inners->length > 0 ) {
1355  $s .= '>' . $this->expand( $inners->item( 0 ), $flags );
1356  if ( $closes->length > 0 ) {
1357  $s .= $this->expand( $closes->item( 0 ), $flags );
1358  }
1359  } else {
1360  $s .= '/>';
1361  }
1362  $out .= $s;
1363  } else {
1364  $params = [
1365  'name' => new PPNode_DOM( $names->item( 0 ) ),
1366  'attr' => $attrs->length > 0 ? new PPNode_DOM( $attrs->item( 0 ) ) : null,
1367  'inner' => $inners->length > 0 ? new PPNode_DOM( $inners->item( 0 ) ) : null,
1368  'close' => $closes->length > 0 ? new PPNode_DOM( $closes->item( 0 ) ) : null,
1369  ];
1370  $out .= $this->parser->extensionSubstitution( $params, $this );
1371  }
1372  } elseif ( $contextNode->nodeName == 'h' ) {
1373  # Heading
1374  $s = $this->expand( $contextNode->childNodes, $flags );
1375 
1376  # Insert a heading marker only for <h> children of <root>
1377  # This is to stop extractSections from going over multiple tree levels
1378  if ( $contextNode->parentNode->nodeName == 'root' && $this->parser->ot['html'] ) {
1379  # Insert heading index marker
1380  $headingIndex = $contextNode->getAttribute( 'i' );
1381  $titleText = $this->title->getPrefixedDBkey();
1382  $this->parser->mHeadings[] = [ $titleText, $headingIndex ];
1383  $serial = count( $this->parser->mHeadings ) - 1;
1384  $marker = Parser::MARKER_PREFIX . "-h-$serial-" . Parser::MARKER_SUFFIX;
1385  $count = $contextNode->getAttribute( 'level' );
1386  $s = substr( $s, 0, $count ) . $marker . substr( $s, $count );
1387  $this->parser->mStripState->addGeneral( $marker, '' );
1388  }
1389  $out .= $s;
1390  } else {
1391  # Generic recursive expansion
1392  $newIterator = $contextNode->childNodes;
1393  }
1394  } else {
1395  throw new MWException( __METHOD__ . ': Invalid parameter type' );
1396  }
1397 
1398  if ( $newIterator !== false ) {
1399  if ( $newIterator instanceof PPNode_DOM ) {
1400  $newIterator = $newIterator->node;
1401  }
1402  $outStack[] = '';
1403  $iteratorStack[] = $newIterator;
1404  $indexStack[] = 0;
1405  } elseif ( $iteratorStack[$level] === false ) {
1406  // Return accumulated value to parent
1407  // With tail recursion
1408  while ( $iteratorStack[$level] === false && $level > 0 ) {
1409  $outStack[$level - 1] .= $out;
1410  array_pop( $outStack );
1411  array_pop( $iteratorStack );
1412  array_pop( $indexStack );
1413  $level--;
1414  }
1415  }
1416  }
1417  --$expansionDepth;
1418  return $outStack[0];
1419  }
1420 
1427  public function implodeWithFlags( $sep, $flags /*, ... */ ) {
1428  $args = array_slice( func_get_args(), 2 );
1429 
1430  $first = true;
1431  $s = '';
1432  foreach ( $args as $root ) {
1433  if ( $root instanceof PPNode_DOM ) {
1434  $root = $root->node;
1435  }
1436  if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
1437  $root = [ $root ];
1438  }
1439  foreach ( $root as $node ) {
1440  if ( $first ) {
1441  $first = false;
1442  } else {
1443  $s .= $sep;
1444  }
1445  $s .= $this->expand( $node, $flags );
1446  }
1447  }
1448  return $s;
1449  }
1450 
1459  public function implode( $sep /*, ... */ ) {
1460  $args = array_slice( func_get_args(), 1 );
1461 
1462  $first = true;
1463  $s = '';
1464  foreach ( $args as $root ) {
1465  if ( $root instanceof PPNode_DOM ) {
1466  $root = $root->node;
1467  }
1468  if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
1469  $root = [ $root ];
1470  }
1471  foreach ( $root as $node ) {
1472  if ( $first ) {
1473  $first = false;
1474  } else {
1475  $s .= $sep;
1476  }
1477  $s .= $this->expand( $node );
1478  }
1479  }
1480  return $s;
1481  }
1482 
1491  public function virtualImplode( $sep /*, ... */ ) {
1492  $args = array_slice( func_get_args(), 1 );
1493  $out = [];
1494  $first = true;
1495 
1496  foreach ( $args as $root ) {
1497  if ( $root instanceof PPNode_DOM ) {
1498  $root = $root->node;
1499  }
1500  if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
1501  $root = [ $root ];
1502  }
1503  foreach ( $root as $node ) {
1504  if ( $first ) {
1505  $first = false;
1506  } else {
1507  $out[] = $sep;
1508  }
1509  $out[] = $node;
1510  }
1511  }
1512  return $out;
1513  }
1514 
1523  public function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) {
1524  $args = array_slice( func_get_args(), 3 );
1525  $out = [ $start ];
1526  $first = true;
1527 
1528  foreach ( $args as $root ) {
1529  if ( $root instanceof PPNode_DOM ) {
1530  $root = $root->node;
1531  }
1532  if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
1533  $root = [ $root ];
1534  }
1535  foreach ( $root as $node ) {
1536  if ( $first ) {
1537  $first = false;
1538  } else {
1539  $out[] = $sep;
1540  }
1541  $out[] = $node;
1542  }
1543  }
1544  $out[] = $end;
1545  return $out;
1546  }
1547 
1548  public function __toString() {
1549  return 'frame{}';
1550  }
1551 
1552  public function getPDBK( $level = false ) {
1553  if ( $level === false ) {
1554  return $this->title->getPrefixedDBkey();
1555  } else {
1556  return $this->titleCache[$level] ?? false;
1557  }
1558  }
1559 
1563  public function getArguments() {
1564  return [];
1565  }
1566 
1570  public function getNumberedArguments() {
1571  return [];
1572  }
1573 
1577  public function getNamedArguments() {
1578  return [];
1579  }
1580 
1586  public function isEmpty() {
1587  return true;
1588  }
1589 
1594  public function getArgument( $name ) {
1595  return false;
1596  }
1597 
1604  public function loopCheck( $title ) {
1605  return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] );
1606  }
1607 
1613  public function isTemplate() {
1614  return false;
1615  }
1616 
1622  public function getTitle() {
1623  return $this->title;
1624  }
1625 
1631  public function setVolatile( $flag = true ) {
1632  $this->volatile = $flag;
1633  }
1634 
1640  public function isVolatile() {
1641  return $this->volatile;
1642  }
1643 
1649  public function setTTL( $ttl ) {
1650  if ( $ttl !== null && ( $this->ttl === null || $ttl < $this->ttl ) ) {
1651  $this->ttl = $ttl;
1652  }
1653  }
1654 
1660  public function getTTL() {
1661  return $this->ttl;
1662  }
1663 }
1664 
1669 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
1670 class PPTemplateFrame_DOM extends PPFrame_DOM {
1671 
1672  public $numberedArgs, $namedArgs;
1673 
1677  public $parent;
1678  public $numberedExpansionCache, $namedExpansionCache;
1679 
1687  public function __construct( $preprocessor, $parent = false, $numberedArgs = [],
1688  $namedArgs = [], $title = false
1689  ) {
1690  parent::__construct( $preprocessor );
1691 
1692  $this->parent = $parent;
1693  $this->numberedArgs = $numberedArgs;
1694  $this->namedArgs = $namedArgs;
1695  $this->title = $title;
1696  $pdbk = $title ? $title->getPrefixedDBkey() : false;
1697  $this->titleCache = $parent->titleCache;
1698  $this->titleCache[] = $pdbk;
1699  $this->loopCheckHash = /*clone*/ $parent->loopCheckHash;
1700  if ( $pdbk !== false ) {
1701  $this->loopCheckHash[$pdbk] = true;
1702  }
1703  $this->depth = $parent->depth + 1;
1704  $this->numberedExpansionCache = $this->namedExpansionCache = [];
1705  }
1706 
1707  public function __toString() {
1708  $s = 'tplframe{';
1709  $first = true;
1710  $args = $this->numberedArgs + $this->namedArgs;
1711  foreach ( $args as $name => $value ) {
1712  if ( $first ) {
1713  $first = false;
1714  } else {
1715  $s .= ', ';
1716  }
1717  $s .= "\"$name\":\"" .
1718  str_replace( '"', '\\"', $value->ownerDocument->saveXML( $value ) ) . '"';
1719  }
1720  $s .= '}';
1721  return $s;
1722  }
1723 
1731  public function cachedExpand( $key, $root, $flags = 0 ) {
1732  if ( isset( $this->parent->childExpansionCache[$key] ) ) {
1733  return $this->parent->childExpansionCache[$key];
1734  }
1735  $retval = $this->expand( $root, $flags );
1736  if ( !$this->isVolatile() ) {
1737  $this->parent->childExpansionCache[$key] = $retval;
1738  }
1739  return $retval;
1740  }
1741 
1747  public function isEmpty() {
1748  return !count( $this->numberedArgs ) && !count( $this->namedArgs );
1749  }
1750 
1751  public function getArguments() {
1752  $arguments = [];
1753  foreach ( array_merge(
1754  array_keys( $this->numberedArgs ),
1755  array_keys( $this->namedArgs ) ) as $key ) {
1756  $arguments[$key] = $this->getArgument( $key );
1757  }
1758  return $arguments;
1759  }
1760 
1761  public function getNumberedArguments() {
1762  $arguments = [];
1763  foreach ( array_keys( $this->numberedArgs ) as $key ) {
1764  $arguments[$key] = $this->getArgument( $key );
1765  }
1766  return $arguments;
1767  }
1768 
1769  public function getNamedArguments() {
1770  $arguments = [];
1771  foreach ( array_keys( $this->namedArgs ) as $key ) {
1772  $arguments[$key] = $this->getArgument( $key );
1773  }
1774  return $arguments;
1775  }
1776 
1781  public function getNumberedArgument( $index ) {
1782  if ( !isset( $this->numberedArgs[$index] ) ) {
1783  return false;
1784  }
1785  if ( !isset( $this->numberedExpansionCache[$index] ) ) {
1786  # No trimming for unnamed arguments
1787  $this->numberedExpansionCache[$index] = $this->parent->expand(
1788  $this->numberedArgs[$index],
1789  PPFrame::STRIP_COMMENTS
1790  );
1791  }
1792  return $this->numberedExpansionCache[$index];
1793  }
1794 
1799  public function getNamedArgument( $name ) {
1800  if ( !isset( $this->namedArgs[$name] ) ) {
1801  return false;
1802  }
1803  if ( !isset( $this->namedExpansionCache[$name] ) ) {
1804  # Trim named arguments post-expand, for backwards compatibility
1805  $this->namedExpansionCache[$name] = trim(
1806  $this->parent->expand( $this->namedArgs[$name], PPFrame::STRIP_COMMENTS ) );
1807  }
1808  return $this->namedExpansionCache[$name];
1809  }
1810 
1815  public function getArgument( $name ) {
1816  $text = $this->getNumberedArgument( $name );
1817  if ( $text === false ) {
1818  $text = $this->getNamedArgument( $name );
1819  }
1820  return $text;
1821  }
1822 
1828  public function isTemplate() {
1829  return true;
1830  }
1831 
1832  public function setVolatile( $flag = true ) {
1833  parent::setVolatile( $flag );
1834  $this->parent->setVolatile( $flag );
1835  }
1836 
1837  public function setTTL( $ttl ) {
1838  parent::setTTL( $ttl );
1839  $this->parent->setTTL( $ttl );
1840  }
1841 }
1842 
1847 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
1848 class PPCustomFrame_DOM extends PPFrame_DOM {
1849 
1850  public $args;
1851 
1852  public function __construct( $preprocessor, $args ) {
1853  parent::__construct( $preprocessor );
1854  $this->args = $args;
1855  }
1856 
1857  public function __toString() {
1858  $s = 'cstmframe{';
1859  $first = true;
1860  foreach ( $this->args as $name => $value ) {
1861  if ( $first ) {
1862  $first = false;
1863  } else {
1864  $s .= ', ';
1865  }
1866  $s .= "\"$name\":\"" .
1867  str_replace( '"', '\\"', $value->__toString() ) . '"';
1868  }
1869  $s .= '}';
1870  return $s;
1871  }
1872 
1876  public function isEmpty() {
1877  return !count( $this->args );
1878  }
1879 
1884  public function getArgument( $index ) {
1885  return $this->args[$index] ?? false;
1886  }
1887 
1888  public function getArguments() {
1889  return $this->args;
1890  }
1891 }
1892 
1896 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
1897 class PPNode_DOM implements PPNode {
1898 
1902  public $node;
1903  public $xpath;
1904 
1905  public function __construct( $node, $xpath = false ) {
1906  $this->node = $node;
1907  }
1908 
1912  public function getXPath() {
1913  if ( $this->xpath === null ) {
1914  $this->xpath = new DOMXPath( $this->node->ownerDocument );
1915  }
1916  return $this->xpath;
1917  }
1918 
1919  public function __toString() {
1920  if ( $this->node instanceof DOMNodeList ) {
1921  $s = '';
1922  foreach ( $this->node as $node ) {
1923  $s .= $node->ownerDocument->saveXML( $node );
1924  }
1925  } else {
1926  $s = $this->node->ownerDocument->saveXML( $this->node );
1927  }
1928  return $s;
1929  }
1930 
1934  public function getChildren() {
1935  return $this->node->childNodes ? new self( $this->node->childNodes ) : false;
1936  }
1937 
1941  public function getFirstChild() {
1942  return $this->node->firstChild ? new self( $this->node->firstChild ) : false;
1943  }
1944 
1948  public function getNextSibling() {
1949  return $this->node->nextSibling ? new self( $this->node->nextSibling ) : false;
1950  }
1951 
1957  public function getChildrenOfType( $type ) {
1958  return new self( $this->getXPath()->query( $type, $this->node ) );
1959  }
1960 
1964  public function getLength() {
1965  if ( $this->node instanceof DOMNodeList ) {
1966  return $this->node->length;
1967  } else {
1968  return false;
1969  }
1970  }
1971 
1976  public function item( $i ) {
1977  $item = $this->node->item( $i );
1978  return $item ? new self( $item ) : false;
1979  }
1980 
1984  public function getName() {
1985  if ( $this->node instanceof DOMNodeList ) {
1986  return '#nodelist';
1987  } else {
1988  return $this->node->nodeName;
1989  }
1990  }
1991 
2001  public function splitArg() {
2002  $xpath = $this->getXPath();
2003  $names = $xpath->query( 'name', $this->node );
2004  $values = $xpath->query( 'value', $this->node );
2005  if ( !$names->length || !$values->length ) {
2006  throw new MWException( 'Invalid brace node passed to ' . __METHOD__ );
2007  }
2008  $name = $names->item( 0 );
2009  $index = $name->getAttribute( 'index' );
2010  return [
2011  'name' => new self( $name ),
2012  'index' => $index,
2013  'value' => new self( $values->item( 0 ) ) ];
2014  }
2015 
2023  public function splitExt() {
2024  $xpath = $this->getXPath();
2025  $names = $xpath->query( 'name', $this->node );
2026  $attrs = $xpath->query( 'attr', $this->node );
2027  $inners = $xpath->query( 'inner', $this->node );
2028  $closes = $xpath->query( 'close', $this->node );
2029  if ( !$names->length || !$attrs->length ) {
2030  throw new MWException( 'Invalid ext node passed to ' . __METHOD__ );
2031  }
2032  $parts = [
2033  'name' => new self( $names->item( 0 ) ),
2034  'attr' => new self( $attrs->item( 0 ) ) ];
2035  if ( $inners->length ) {
2036  $parts['inner'] = new self( $inners->item( 0 ) );
2037  }
2038  if ( $closes->length ) {
2039  $parts['close'] = new self( $closes->item( 0 ) );
2040  }
2041  return $parts;
2042  }
2043 
2049  public function splitHeading() {
2050  if ( $this->getName() !== 'h' ) {
2051  throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
2052  }
2053  return [
2054  'i' => $this->node->getAttribute( 'i' ),
2055  'level' => $this->node->getAttribute( 'level' ),
2056  'contents' => $this->getChildren()
2057  ];
2058  }
2059 }
Preprocessor_DOM\preprocessToXml
preprocessToXml( $text, $flags=0)
Definition: Preprocessor_DOM.php:193
part
in this case you re responsible for computing and outputting the entire conflict part
Definition: hooks.txt:1423
Preprocessor_DOM
Definition: Preprocessor_DOM.php:28
PPDPart
Definition: Preprocessor_DOM.php:1032
PPNode_DOM
Definition: Preprocessor_DOM.php:1897
PPNode_DOM\item
item( $i)
Definition: Preprocessor_DOM.php:1976
PPNode_DOM\$xpath
$xpath
Definition: Preprocessor_DOM.php:1903
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:35
Preprocessor_DOM\$parser
Parser $parser
Definition: Preprocessor_DOM.php:33
PPNode_DOM\$node
DOMElement $node
Definition: Preprocessor_DOM.php:1902
captcha-old.count
count
Definition: captcha-old.py:249
$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 '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. '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 '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 since 1.28! 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:1983
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:186
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:85
value
if( $inline) $status value
Definition: SyntaxHighlight.php:345
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:2049
PPNode_DOM\getXPath
getXPath()
Definition: Preprocessor_DOM.php:1912
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:1905
name
and how to run hooks for an and one after Each event has a name
Definition: hooks.txt:6
PPCustomFrame_DOM
Expansion frame with custom arguments.
Definition: Preprocessor_DOM.php:1848
PPNode_DOM\getName
getName()
Definition: Preprocessor_DOM.php:1984
MWException
MediaWiki exception.
Definition: MWException.php:26
PPNode_DOM\getLength
getLength()
Definition: Preprocessor_DOM.php:1964
PPCustomFrame_DOM\getArgument
getArgument( $index)
Definition: Preprocessor_DOM.php:1884
$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:360
PPNode_DOM\getChildren
getChildren()
Definition: Preprocessor_DOM.php:1934
Preprocessor_DOM\newCustomFrame
newCustomFrame( $args)
Definition: Preprocessor_DOM.php:63
PPNode_DOM\getChildrenOfType
getChildrenOfType( $type)
Definition: Preprocessor_DOM.php:1957
$wgDisableLangConversion
$wgDisableLangConversion
Whether to enable language variant conversion.
Definition: DefaultSettings.php:3085
Preprocessor_DOM\memCheck
memCheck()
Definition: Preprocessor_DOM.php:113
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:271
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:49
PPFrame_DOM
An expansion frame, used as a context to expand the result of preprocessToObj()
Definition: Preprocessor_DOM.php:1053
title
title
Definition: parserTests.txt:245
PPDStack
Stack class to help Preprocessor::preprocessToObj()
Definition: Preprocessor_DOM.php:831
PPNode_DOM\__toString
__toString()
Definition: Preprocessor_DOM.php:1919
text
This list may contain false positives That usually means there is additional text with links below the first Each row contains links to the first and second as well as the first line of the second redirect text
Definition: All_system_messages.txt:1267
$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
Preprocessor_DOM\CACHE_PREFIX
const CACHE_PREFIX
Definition: Preprocessor_DOM.php:37
PPCustomFrame_DOM\isEmpty
isEmpty()
Definition: Preprocessor_DOM.php:1876
PPNode_DOM\splitArg
splitArg()
Split a "<part>" node into an associative array containing:
Definition: Preprocessor_DOM.php:2001
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:72
Preprocessor_DOM\newFrame
newFrame()
Definition: Preprocessor_DOM.php:55
PPNode_DOM\getFirstChild
getFirstChild()
Definition: Preprocessor_DOM.php:1941
captcha-old.parser
parser
Definition: captcha-old.py:210
PPCustomFrame_DOM\getArguments
getArguments()
Definition: Preprocessor_DOM.php:1888
Preprocessor\cacheGetTree
cacheGetTree( $text, $flags)
Attempt to load a precomputed document tree for some given wikitext from the cache.
Definition: Preprocessor.php:96
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
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:2023
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:1948
names
alter the names
Definition: COPYING.txt:329
$type
$type
Definition: testCompression.php:48