MediaWiki  1.32.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 
861  public function getCurrentPart() {
862  if ( $this->top === false ) {
863  return false;
864  } else {
865  return $this->top->getCurrentPart();
866  }
867  }
868 
869  public function push( $data ) {
870  if ( $data instanceof $this->elementClass ) {
871  $this->stack[] = $data;
872  } else {
873  $class = $this->elementClass;
874  $this->stack[] = new $class( $data );
875  }
876  $this->top = $this->stack[count( $this->stack ) - 1];
877  $this->accum =& $this->top->getAccum();
878  }
879 
880  public function pop() {
881  if ( !count( $this->stack ) ) {
882  throw new MWException( __METHOD__ . ': no elements remaining' );
883  }
884  $temp = array_pop( $this->stack );
885 
886  if ( count( $this->stack ) ) {
887  $this->top = $this->stack[count( $this->stack ) - 1];
888  $this->accum =& $this->top->getAccum();
889  } else {
890  $this->top = self::$false;
891  $this->accum =& $this->rootAccum;
892  }
893  return $temp;
894  }
895 
896  public function addPart( $s = '' ) {
897  $this->top->addPart( $s );
898  $this->accum =& $this->top->getAccum();
899  }
900 
904  public function getFlags() {
905  if ( !count( $this->stack ) ) {
906  return [
907  'findEquals' => false,
908  'findPipe' => false,
909  'inHeading' => false,
910  ];
911  } else {
912  return $this->top->getFlags();
913  }
914  }
915 }
916 
920 class PPDStackElement {
924  public $open;
925 
929  public $close;
930 
935  public $savedPrefix = '';
936 
940  public $count;
941 
945  public $parts;
946 
951  public $lineStart;
952 
953  public $partClass = PPDPart::class;
954 
955  public function __construct( $data = [] ) {
956  $class = $this->partClass;
957  $this->parts = [ new $class ];
958 
959  foreach ( $data as $name => $value ) {
960  $this->$name = $value;
961  }
962  }
963 
964  public function &getAccum() {
965  return $this->parts[count( $this->parts ) - 1]->out;
966  }
967 
968  public function addPart( $s = '' ) {
969  $class = $this->partClass;
970  $this->parts[] = new $class( $s );
971  }
972 
973  public function getCurrentPart() {
974  return $this->parts[count( $this->parts ) - 1];
975  }
976 
980  public function getFlags() {
981  $partCount = count( $this->parts );
982  $findPipe = $this->open != "\n" && $this->open != '[';
983  return [
984  'findPipe' => $findPipe,
985  'findEquals' => $findPipe && $partCount > 1 && !isset( $this->parts[$partCount - 1]->eqpos ),
986  'inHeading' => $this->open == "\n",
987  ];
988  }
989 
996  public function breakSyntax( $openingCount = false ) {
997  if ( $this->open == "\n" ) {
998  $s = $this->savedPrefix . $this->parts[0]->out;
999  } else {
1000  if ( $openingCount === false ) {
1001  $openingCount = $this->count;
1002  }
1003  $s = substr( $this->open, 0, -1 );
1004  $s .= str_repeat(
1005  substr( $this->open, -1 ),
1006  $openingCount - strlen( $s )
1007  );
1008  $s = $this->savedPrefix . $s;
1009  $first = true;
1010  foreach ( $this->parts as $part ) {
1011  if ( $first ) {
1012  $first = false;
1013  } else {
1014  $s .= '|';
1015  }
1016  $s .= $part->out;
1017  }
1018  }
1019  return $s;
1020  }
1021 }
1022 
1026 class PPDPart {
1030  public $out;
1031 
1032  // Optional member variables:
1033  // eqpos Position of equals sign in output accumulator
1034  // commentEnd Past-the-end input pointer for the last comment encountered
1035  // visualEnd Past-the-end input pointer for the end of the accumulator minus comments
1036 
1037  public function __construct( $out = '' ) {
1038  $this->out = $out;
1039  }
1040 }
1041 
1046 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
1047 class PPFrame_DOM implements PPFrame {
1048 
1052  public $preprocessor;
1053 
1057  public $parser;
1058 
1062  public $title;
1063  public $titleCache;
1064 
1069  public $loopCheckHash;
1070 
1075  public $depth;
1076 
1077  private $volatile = false;
1078  private $ttl = null;
1079 
1083  protected $childExpansionCache;
1084 
1089  public function __construct( $preprocessor ) {
1090  $this->preprocessor = $preprocessor;
1091  $this->parser = $preprocessor->parser;
1092  $this->title = $this->parser->mTitle;
1093  $this->titleCache = [ $this->title ? $this->title->getPrefixedDBkey() : false ];
1094  $this->loopCheckHash = [];
1095  $this->depth = 0;
1096  $this->childExpansionCache = [];
1097  }
1098 
1108  public function newChild( $args = false, $title = false, $indexOffset = 0 ) {
1109  $namedArgs = [];
1110  $numberedArgs = [];
1111  if ( $title === false ) {
1112  $title = $this->title;
1113  }
1114  if ( $args !== false ) {
1115  $xpath = false;
1116  if ( $args instanceof PPNode ) {
1117  $args = $args->node;
1118  }
1119  foreach ( $args as $arg ) {
1120  if ( $arg instanceof PPNode ) {
1121  $arg = $arg->node;
1122  }
1123  if ( !$xpath || $xpath->document !== $arg->ownerDocument ) {
1124  $xpath = new DOMXPath( $arg->ownerDocument );
1125  }
1126 
1127  $nameNodes = $xpath->query( 'name', $arg );
1128  $value = $xpath->query( 'value', $arg );
1129  if ( $nameNodes->item( 0 )->hasAttributes() ) {
1130  // Numbered parameter
1131  $index = $nameNodes->item( 0 )->attributes->getNamedItem( 'index' )->textContent;
1132  $index = $index - $indexOffset;
1133  if ( isset( $namedArgs[$index] ) || isset( $numberedArgs[$index] ) ) {
1134  $this->parser->getOutput()->addWarning( wfMessage( 'duplicate-args-warning',
1135  wfEscapeWikiText( $this->title ),
1136  wfEscapeWikiText( $title ),
1137  wfEscapeWikiText( $index ) )->text() );
1138  $this->parser->addTrackingCategory( 'duplicate-args-category' );
1139  }
1140  $numberedArgs[$index] = $value->item( 0 );
1141  unset( $namedArgs[$index] );
1142  } else {
1143  // Named parameter
1144  $name = trim( $this->expand( $nameNodes->item( 0 ), PPFrame::STRIP_COMMENTS ) );
1145  if ( isset( $namedArgs[$name] ) || isset( $numberedArgs[$name] ) ) {
1146  $this->parser->getOutput()->addWarning( wfMessage( 'duplicate-args-warning',
1147  wfEscapeWikiText( $this->title ),
1148  wfEscapeWikiText( $title ),
1149  wfEscapeWikiText( $name ) )->text() );
1150  $this->parser->addTrackingCategory( 'duplicate-args-category' );
1151  }
1152  $namedArgs[$name] = $value->item( 0 );
1153  unset( $numberedArgs[$name] );
1154  }
1155  }
1156  }
1157  return new PPTemplateFrame_DOM( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title );
1158  }
1159 
1167  public function cachedExpand( $key, $root, $flags = 0 ) {
1168  // we don't have a parent, so we don't have a cache
1169  return $this->expand( $root, $flags );
1170  }
1171 
1178  public function expand( $root, $flags = 0 ) {
1179  static $expansionDepth = 0;
1180  if ( is_string( $root ) ) {
1181  return $root;
1182  }
1183 
1184  if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount() ) {
1185  $this->parser->limitationWarn( 'node-count-exceeded',
1186  $this->parser->mPPNodeCount,
1187  $this->parser->mOptions->getMaxPPNodeCount()
1188  );
1189  return '<span class="error">Node-count limit exceeded</span>';
1190  }
1191 
1192  if ( $expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth() ) {
1193  $this->parser->limitationWarn( 'expansion-depth-exceeded',
1194  $expansionDepth,
1195  $this->parser->mOptions->getMaxPPExpandDepth()
1196  );
1197  return '<span class="error">Expansion depth limit exceeded</span>';
1198  }
1199  ++$expansionDepth;
1200  if ( $expansionDepth > $this->parser->mHighestExpansionDepth ) {
1201  $this->parser->mHighestExpansionDepth = $expansionDepth;
1202  }
1203 
1204  if ( $root instanceof PPNode_DOM ) {
1205  $root = $root->node;
1206  }
1207  if ( $root instanceof DOMDocument ) {
1208  $root = $root->documentElement;
1209  }
1210 
1211  $outStack = [ '', '' ];
1212  $iteratorStack = [ false, $root ];
1213  $indexStack = [ 0, 0 ];
1214 
1215  while ( count( $iteratorStack ) > 1 ) {
1216  $level = count( $outStack ) - 1;
1217  $iteratorNode =& $iteratorStack[$level];
1218  $out =& $outStack[$level];
1219  $index =& $indexStack[$level];
1220 
1221  if ( $iteratorNode instanceof PPNode_DOM ) {
1222  $iteratorNode = $iteratorNode->node;
1223  }
1224 
1225  if ( is_array( $iteratorNode ) ) {
1226  if ( $index >= count( $iteratorNode ) ) {
1227  // All done with this iterator
1228  $iteratorStack[$level] = false;
1229  $contextNode = false;
1230  } else {
1231  $contextNode = $iteratorNode[$index];
1232  $index++;
1233  }
1234  } elseif ( $iteratorNode instanceof DOMNodeList ) {
1235  if ( $index >= $iteratorNode->length ) {
1236  // All done with this iterator
1237  $iteratorStack[$level] = false;
1238  $contextNode = false;
1239  } else {
1240  $contextNode = $iteratorNode->item( $index );
1241  $index++;
1242  }
1243  } else {
1244  // Copy to $contextNode and then delete from iterator stack,
1245  // because this is not an iterator but we do have to execute it once
1246  $contextNode = $iteratorStack[$level];
1247  $iteratorStack[$level] = false;
1248  }
1249 
1250  if ( $contextNode instanceof PPNode_DOM ) {
1251  $contextNode = $contextNode->node;
1252  }
1253 
1254  $newIterator = false;
1255 
1256  if ( $contextNode === false ) {
1257  // nothing to do
1258  } elseif ( is_string( $contextNode ) ) {
1259  $out .= $contextNode;
1260  } elseif ( is_array( $contextNode ) || $contextNode instanceof DOMNodeList ) {
1261  $newIterator = $contextNode;
1262  } elseif ( $contextNode instanceof DOMNode ) {
1263  if ( $contextNode->nodeType == XML_TEXT_NODE ) {
1264  $out .= $contextNode->nodeValue;
1265  } elseif ( $contextNode->nodeName == 'template' ) {
1266  # Double-brace expansion
1267  $xpath = new DOMXPath( $contextNode->ownerDocument );
1268  $titles = $xpath->query( 'title', $contextNode );
1269  $title = $titles->item( 0 );
1270  $parts = $xpath->query( 'part', $contextNode );
1271  if ( $flags & PPFrame::NO_TEMPLATES ) {
1272  $newIterator = $this->virtualBracketedImplode( '{{', '|', '}}', $title, $parts );
1273  } else {
1274  $lineStart = $contextNode->getAttribute( 'lineStart' );
1275  $params = [
1276  'title' => new PPNode_DOM( $title ),
1277  'parts' => new PPNode_DOM( $parts ),
1278  'lineStart' => $lineStart ];
1279  $ret = $this->parser->braceSubstitution( $params, $this );
1280  if ( isset( $ret['object'] ) ) {
1281  $newIterator = $ret['object'];
1282  } else {
1283  $out .= $ret['text'];
1284  }
1285  }
1286  } elseif ( $contextNode->nodeName == 'tplarg' ) {
1287  # Triple-brace expansion
1288  $xpath = new DOMXPath( $contextNode->ownerDocument );
1289  $titles = $xpath->query( 'title', $contextNode );
1290  $title = $titles->item( 0 );
1291  $parts = $xpath->query( 'part', $contextNode );
1292  if ( $flags & PPFrame::NO_ARGS ) {
1293  $newIterator = $this->virtualBracketedImplode( '{{{', '|', '}}}', $title, $parts );
1294  } else {
1295  $params = [
1296  'title' => new PPNode_DOM( $title ),
1297  'parts' => new PPNode_DOM( $parts ) ];
1298  $ret = $this->parser->argSubstitution( $params, $this );
1299  if ( isset( $ret['object'] ) ) {
1300  $newIterator = $ret['object'];
1301  } else {
1302  $out .= $ret['text'];
1303  }
1304  }
1305  } elseif ( $contextNode->nodeName == 'comment' ) {
1306  # HTML-style comment
1307  # Remove it in HTML, pre+remove and STRIP_COMMENTS modes
1308  # Not in RECOVER_COMMENTS mode (msgnw) though.
1309  if ( ( $this->parser->ot['html']
1310  || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() )
1311  || ( $flags & PPFrame::STRIP_COMMENTS )
1312  ) && !( $flags & PPFrame::RECOVER_COMMENTS )
1313  ) {
1314  $out .= '';
1315  } elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) {
1316  # Add a strip marker in PST mode so that pstPass2() can
1317  # run some old-fashioned regexes on the result.
1318  # Not in RECOVER_COMMENTS mode (extractSections) though.
1319  $out .= $this->parser->insertStripItem( $contextNode->textContent );
1320  } else {
1321  # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
1322  $out .= $contextNode->textContent;
1323  }
1324  } elseif ( $contextNode->nodeName == 'ignore' ) {
1325  # Output suppression used by <includeonly> etc.
1326  # OT_WIKI will only respect <ignore> in substed templates.
1327  # The other output types respect it unless NO_IGNORE is set.
1328  # extractSections() sets NO_IGNORE and so never respects it.
1329  if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] )
1330  || ( $flags & PPFrame::NO_IGNORE )
1331  ) {
1332  $out .= $contextNode->textContent;
1333  } else {
1334  $out .= '';
1335  }
1336  } elseif ( $contextNode->nodeName == 'ext' ) {
1337  # Extension tag
1338  $xpath = new DOMXPath( $contextNode->ownerDocument );
1339  $names = $xpath->query( 'name', $contextNode );
1340  $attrs = $xpath->query( 'attr', $contextNode );
1341  $inners = $xpath->query( 'inner', $contextNode );
1342  $closes = $xpath->query( 'close', $contextNode );
1343  if ( $flags & PPFrame::NO_TAGS ) {
1344  $s = '<' . $this->expand( $names->item( 0 ), $flags );
1345  if ( $attrs->length > 0 ) {
1346  $s .= $this->expand( $attrs->item( 0 ), $flags );
1347  }
1348  if ( $inners->length > 0 ) {
1349  $s .= '>' . $this->expand( $inners->item( 0 ), $flags );
1350  if ( $closes->length > 0 ) {
1351  $s .= $this->expand( $closes->item( 0 ), $flags );
1352  }
1353  } else {
1354  $s .= '/>';
1355  }
1356  $out .= $s;
1357  } else {
1358  $params = [
1359  'name' => new PPNode_DOM( $names->item( 0 ) ),
1360  'attr' => $attrs->length > 0 ? new PPNode_DOM( $attrs->item( 0 ) ) : null,
1361  'inner' => $inners->length > 0 ? new PPNode_DOM( $inners->item( 0 ) ) : null,
1362  'close' => $closes->length > 0 ? new PPNode_DOM( $closes->item( 0 ) ) : null,
1363  ];
1364  $out .= $this->parser->extensionSubstitution( $params, $this );
1365  }
1366  } elseif ( $contextNode->nodeName == 'h' ) {
1367  # Heading
1368  $s = $this->expand( $contextNode->childNodes, $flags );
1369 
1370  # Insert a heading marker only for <h> children of <root>
1371  # This is to stop extractSections from going over multiple tree levels
1372  if ( $contextNode->parentNode->nodeName == 'root' && $this->parser->ot['html'] ) {
1373  # Insert heading index marker
1374  $headingIndex = $contextNode->getAttribute( 'i' );
1375  $titleText = $this->title->getPrefixedDBkey();
1376  $this->parser->mHeadings[] = [ $titleText, $headingIndex ];
1377  $serial = count( $this->parser->mHeadings ) - 1;
1378  $marker = Parser::MARKER_PREFIX . "-h-$serial-" . Parser::MARKER_SUFFIX;
1379  $count = $contextNode->getAttribute( 'level' );
1380  $s = substr( $s, 0, $count ) . $marker . substr( $s, $count );
1381  $this->parser->mStripState->addGeneral( $marker, '' );
1382  }
1383  $out .= $s;
1384  } else {
1385  # Generic recursive expansion
1386  $newIterator = $contextNode->childNodes;
1387  }
1388  } else {
1389  throw new MWException( __METHOD__ . ': Invalid parameter type' );
1390  }
1391 
1392  if ( $newIterator !== false ) {
1393  if ( $newIterator instanceof PPNode_DOM ) {
1394  $newIterator = $newIterator->node;
1395  }
1396  $outStack[] = '';
1397  $iteratorStack[] = $newIterator;
1398  $indexStack[] = 0;
1399  } elseif ( $iteratorStack[$level] === false ) {
1400  // Return accumulated value to parent
1401  // With tail recursion
1402  while ( $iteratorStack[$level] === false && $level > 0 ) {
1403  $outStack[$level - 1] .= $out;
1404  array_pop( $outStack );
1405  array_pop( $iteratorStack );
1406  array_pop( $indexStack );
1407  $level--;
1408  }
1409  }
1410  }
1411  --$expansionDepth;
1412  return $outStack[0];
1413  }
1414 
1421  public function implodeWithFlags( $sep, $flags /*, ... */ ) {
1422  $args = array_slice( func_get_args(), 2 );
1423 
1424  $first = true;
1425  $s = '';
1426  foreach ( $args as $root ) {
1427  if ( $root instanceof PPNode_DOM ) {
1428  $root = $root->node;
1429  }
1430  if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
1431  $root = [ $root ];
1432  }
1433  foreach ( $root as $node ) {
1434  if ( $first ) {
1435  $first = false;
1436  } else {
1437  $s .= $sep;
1438  }
1439  $s .= $this->expand( $node, $flags );
1440  }
1441  }
1442  return $s;
1443  }
1444 
1453  public function implode( $sep /*, ... */ ) {
1454  $args = array_slice( func_get_args(), 1 );
1455 
1456  $first = true;
1457  $s = '';
1458  foreach ( $args as $root ) {
1459  if ( $root instanceof PPNode_DOM ) {
1460  $root = $root->node;
1461  }
1462  if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
1463  $root = [ $root ];
1464  }
1465  foreach ( $root as $node ) {
1466  if ( $first ) {
1467  $first = false;
1468  } else {
1469  $s .= $sep;
1470  }
1471  $s .= $this->expand( $node );
1472  }
1473  }
1474  return $s;
1475  }
1476 
1485  public function virtualImplode( $sep /*, ... */ ) {
1486  $args = array_slice( func_get_args(), 1 );
1487  $out = [];
1488  $first = true;
1489 
1490  foreach ( $args as $root ) {
1491  if ( $root instanceof PPNode_DOM ) {
1492  $root = $root->node;
1493  }
1494  if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
1495  $root = [ $root ];
1496  }
1497  foreach ( $root as $node ) {
1498  if ( $first ) {
1499  $first = false;
1500  } else {
1501  $out[] = $sep;
1502  }
1503  $out[] = $node;
1504  }
1505  }
1506  return $out;
1507  }
1508 
1517  public function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) {
1518  $args = array_slice( func_get_args(), 3 );
1519  $out = [ $start ];
1520  $first = true;
1521 
1522  foreach ( $args as $root ) {
1523  if ( $root instanceof PPNode_DOM ) {
1524  $root = $root->node;
1525  }
1526  if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
1527  $root = [ $root ];
1528  }
1529  foreach ( $root as $node ) {
1530  if ( $first ) {
1531  $first = false;
1532  } else {
1533  $out[] = $sep;
1534  }
1535  $out[] = $node;
1536  }
1537  }
1538  $out[] = $end;
1539  return $out;
1540  }
1541 
1542  public function __toString() {
1543  return 'frame{}';
1544  }
1545 
1546  public function getPDBK( $level = false ) {
1547  if ( $level === false ) {
1548  return $this->title->getPrefixedDBkey();
1549  } else {
1550  return $this->titleCache[$level] ?? false;
1551  }
1552  }
1553 
1557  public function getArguments() {
1558  return [];
1559  }
1560 
1564  public function getNumberedArguments() {
1565  return [];
1566  }
1567 
1571  public function getNamedArguments() {
1572  return [];
1573  }
1574 
1580  public function isEmpty() {
1581  return true;
1582  }
1583 
1588  public function getArgument( $name ) {
1589  return false;
1590  }
1591 
1598  public function loopCheck( $title ) {
1599  return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] );
1600  }
1601 
1607  public function isTemplate() {
1608  return false;
1609  }
1610 
1616  public function getTitle() {
1617  return $this->title;
1618  }
1619 
1625  public function setVolatile( $flag = true ) {
1626  $this->volatile = $flag;
1627  }
1628 
1634  public function isVolatile() {
1635  return $this->volatile;
1636  }
1637 
1643  public function setTTL( $ttl ) {
1644  if ( $ttl !== null && ( $this->ttl === null || $ttl < $this->ttl ) ) {
1645  $this->ttl = $ttl;
1646  }
1647  }
1648 
1654  public function getTTL() {
1655  return $this->ttl;
1656  }
1657 }
1658 
1663 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
1664 class PPTemplateFrame_DOM extends PPFrame_DOM {
1665 
1666  public $numberedArgs, $namedArgs;
1667 
1671  public $parent;
1672  public $numberedExpansionCache, $namedExpansionCache;
1673 
1681  public function __construct( $preprocessor, $parent = false, $numberedArgs = [],
1682  $namedArgs = [], $title = false
1683  ) {
1684  parent::__construct( $preprocessor );
1685 
1686  $this->parent = $parent;
1687  $this->numberedArgs = $numberedArgs;
1688  $this->namedArgs = $namedArgs;
1689  $this->title = $title;
1690  $pdbk = $title ? $title->getPrefixedDBkey() : false;
1691  $this->titleCache = $parent->titleCache;
1692  $this->titleCache[] = $pdbk;
1693  $this->loopCheckHash = /*clone*/ $parent->loopCheckHash;
1694  if ( $pdbk !== false ) {
1695  $this->loopCheckHash[$pdbk] = true;
1696  }
1697  $this->depth = $parent->depth + 1;
1698  $this->numberedExpansionCache = $this->namedExpansionCache = [];
1699  }
1700 
1701  public function __toString() {
1702  $s = 'tplframe{';
1703  $first = true;
1704  $args = $this->numberedArgs + $this->namedArgs;
1705  foreach ( $args as $name => $value ) {
1706  if ( $first ) {
1707  $first = false;
1708  } else {
1709  $s .= ', ';
1710  }
1711  $s .= "\"$name\":\"" .
1712  str_replace( '"', '\\"', $value->ownerDocument->saveXML( $value ) ) . '"';
1713  }
1714  $s .= '}';
1715  return $s;
1716  }
1717 
1725  public function cachedExpand( $key, $root, $flags = 0 ) {
1726  if ( isset( $this->parent->childExpansionCache[$key] ) ) {
1727  return $this->parent->childExpansionCache[$key];
1728  }
1729  $retval = $this->expand( $root, $flags );
1730  if ( !$this->isVolatile() ) {
1731  $this->parent->childExpansionCache[$key] = $retval;
1732  }
1733  return $retval;
1734  }
1735 
1741  public function isEmpty() {
1742  return !count( $this->numberedArgs ) && !count( $this->namedArgs );
1743  }
1744 
1745  public function getArguments() {
1746  $arguments = [];
1747  foreach ( array_merge(
1748  array_keys( $this->numberedArgs ),
1749  array_keys( $this->namedArgs ) ) as $key ) {
1750  $arguments[$key] = $this->getArgument( $key );
1751  }
1752  return $arguments;
1753  }
1754 
1755  public function getNumberedArguments() {
1756  $arguments = [];
1757  foreach ( array_keys( $this->numberedArgs ) as $key ) {
1758  $arguments[$key] = $this->getArgument( $key );
1759  }
1760  return $arguments;
1761  }
1762 
1763  public function getNamedArguments() {
1764  $arguments = [];
1765  foreach ( array_keys( $this->namedArgs ) as $key ) {
1766  $arguments[$key] = $this->getArgument( $key );
1767  }
1768  return $arguments;
1769  }
1770 
1775  public function getNumberedArgument( $index ) {
1776  if ( !isset( $this->numberedArgs[$index] ) ) {
1777  return false;
1778  }
1779  if ( !isset( $this->numberedExpansionCache[$index] ) ) {
1780  # No trimming for unnamed arguments
1781  $this->numberedExpansionCache[$index] = $this->parent->expand(
1782  $this->numberedArgs[$index],
1783  PPFrame::STRIP_COMMENTS
1784  );
1785  }
1786  return $this->numberedExpansionCache[$index];
1787  }
1788 
1793  public function getNamedArgument( $name ) {
1794  if ( !isset( $this->namedArgs[$name] ) ) {
1795  return false;
1796  }
1797  if ( !isset( $this->namedExpansionCache[$name] ) ) {
1798  # Trim named arguments post-expand, for backwards compatibility
1799  $this->namedExpansionCache[$name] = trim(
1800  $this->parent->expand( $this->namedArgs[$name], PPFrame::STRIP_COMMENTS ) );
1801  }
1802  return $this->namedExpansionCache[$name];
1803  }
1804 
1809  public function getArgument( $name ) {
1810  $text = $this->getNumberedArgument( $name );
1811  if ( $text === false ) {
1812  $text = $this->getNamedArgument( $name );
1813  }
1814  return $text;
1815  }
1816 
1822  public function isTemplate() {
1823  return true;
1824  }
1825 
1826  public function setVolatile( $flag = true ) {
1827  parent::setVolatile( $flag );
1828  $this->parent->setVolatile( $flag );
1829  }
1830 
1831  public function setTTL( $ttl ) {
1832  parent::setTTL( $ttl );
1833  $this->parent->setTTL( $ttl );
1834  }
1835 }
1836 
1841 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
1842 class PPCustomFrame_DOM extends PPFrame_DOM {
1843 
1844  public $args;
1845 
1846  public function __construct( $preprocessor, $args ) {
1847  parent::__construct( $preprocessor );
1848  $this->args = $args;
1849  }
1850 
1851  public function __toString() {
1852  $s = 'cstmframe{';
1853  $first = true;
1854  foreach ( $this->args as $name => $value ) {
1855  if ( $first ) {
1856  $first = false;
1857  } else {
1858  $s .= ', ';
1859  }
1860  $s .= "\"$name\":\"" .
1861  str_replace( '"', '\\"', $value->__toString() ) . '"';
1862  }
1863  $s .= '}';
1864  return $s;
1865  }
1866 
1870  public function isEmpty() {
1871  return !count( $this->args );
1872  }
1873 
1878  public function getArgument( $index ) {
1879  if ( !isset( $this->args[$index] ) ) {
1880  return false;
1881  }
1882  return $this->args[$index];
1883  }
1884 
1885  public function getArguments() {
1886  return $this->args;
1887  }
1888 }
1889 
1893 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
1894 class PPNode_DOM implements PPNode {
1895 
1899  public $node;
1900  public $xpath;
1901 
1902  public function __construct( $node, $xpath = false ) {
1903  $this->node = $node;
1904  }
1905 
1909  public function getXPath() {
1910  if ( $this->xpath === null ) {
1911  $this->xpath = new DOMXPath( $this->node->ownerDocument );
1912  }
1913  return $this->xpath;
1914  }
1915 
1916  public function __toString() {
1917  if ( $this->node instanceof DOMNodeList ) {
1918  $s = '';
1919  foreach ( $this->node as $node ) {
1920  $s .= $node->ownerDocument->saveXML( $node );
1921  }
1922  } else {
1923  $s = $this->node->ownerDocument->saveXML( $this->node );
1924  }
1925  return $s;
1926  }
1927 
1931  public function getChildren() {
1932  return $this->node->childNodes ? new self( $this->node->childNodes ) : false;
1933  }
1934 
1938  public function getFirstChild() {
1939  return $this->node->firstChild ? new self( $this->node->firstChild ) : false;
1940  }
1941 
1945  public function getNextSibling() {
1946  return $this->node->nextSibling ? new self( $this->node->nextSibling ) : false;
1947  }
1948 
1954  public function getChildrenOfType( $type ) {
1955  return new self( $this->getXPath()->query( $type, $this->node ) );
1956  }
1957 
1961  public function getLength() {
1962  if ( $this->node instanceof DOMNodeList ) {
1963  return $this->node->length;
1964  } else {
1965  return false;
1966  }
1967  }
1968 
1973  public function item( $i ) {
1974  $item = $this->node->item( $i );
1975  return $item ? new self( $item ) : false;
1976  }
1977 
1981  public function getName() {
1982  if ( $this->node instanceof DOMNodeList ) {
1983  return '#nodelist';
1984  } else {
1985  return $this->node->nodeName;
1986  }
1987  }
1988 
1998  public function splitArg() {
1999  $xpath = $this->getXPath();
2000  $names = $xpath->query( 'name', $this->node );
2001  $values = $xpath->query( 'value', $this->node );
2002  if ( !$names->length || !$values->length ) {
2003  throw new MWException( 'Invalid brace node passed to ' . __METHOD__ );
2004  }
2005  $name = $names->item( 0 );
2006  $index = $name->getAttribute( 'index' );
2007  return [
2008  'name' => new self( $name ),
2009  'index' => $index,
2010  'value' => new self( $values->item( 0 ) ) ];
2011  }
2012 
2020  public function splitExt() {
2021  $xpath = $this->getXPath();
2022  $names = $xpath->query( 'name', $this->node );
2023  $attrs = $xpath->query( 'attr', $this->node );
2024  $inners = $xpath->query( 'inner', $this->node );
2025  $closes = $xpath->query( 'close', $this->node );
2026  if ( !$names->length || !$attrs->length ) {
2027  throw new MWException( 'Invalid ext node passed to ' . __METHOD__ );
2028  }
2029  $parts = [
2030  'name' => new self( $names->item( 0 ) ),
2031  'attr' => new self( $attrs->item( 0 ) ) ];
2032  if ( $inners->length ) {
2033  $parts['inner'] = new self( $inners->item( 0 ) );
2034  }
2035  if ( $closes->length ) {
2036  $parts['close'] = new self( $closes->item( 0 ) );
2037  }
2038  return $parts;
2039  }
2040 
2046  public function splitHeading() {
2047  if ( $this->getName() !== 'h' ) {
2048  throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
2049  }
2050  return [
2051  'i' => $this->node->getAttribute( 'i' ),
2052  'level' => $this->node->getAttribute( 'level' ),
2053  'contents' => $this->getChildren()
2054  ];
2055  }
2056 }
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:1462
Preprocessor_DOM
Definition: Preprocessor_DOM.php:28
PPDPart
Definition: Preprocessor_DOM.php:1026
PPNode_DOM
Definition: Preprocessor_DOM.php:1894
PPNode_DOM\item
item( $i)
Definition: Preprocessor_DOM.php:1973
PPNode_DOM\$xpath
$xpath
Definition: Preprocessor_DOM.php:1900
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:1899
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. 'LanguageGetMagic':DEPRECATED since 1.16! 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 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:2034
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:187
a
</source > ! result< div class="mw-highlight mw-content-ltr" dir="ltr">< pre >< span ></span >< span class="kd"> var</span >< span class="nx"> a</span >< span class="p"></span ></pre ></div > ! end ! test Multiline< source/> in lists !input *< source > a b</source > *foo< source > a b</source > ! html< ul >< li >< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > a b</pre ></div ></li ></ul >< ul >< li > foo< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > a b</pre ></div ></li ></ul > ! html tidy< ul >< li >< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > a b</pre ></div ></li ></ul >< ul >< li > foo< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > a b</pre ></div ></li ></ul > ! end ! test Custom attributes !input< source lang="javascript" id="foo" class="bar" dir="rtl" style="font-size: larger;"> var a
Definition: parserTests.txt:89
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:2046
PPNode_DOM\getXPath
getXPath()
Definition: Preprocessor_DOM.php:1909
names
alter the names
Definition: COPYING.txt:329
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
Preprocessor
Definition: Preprocessor.php:29
PPNode_DOM\__construct
__construct( $node, $xpath=false)
Definition: Preprocessor_DOM.php:1902
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:1842
PPNode_DOM\getName
getName()
Definition: Preprocessor_DOM.php:1981
MWException
MediaWiki exception.
Definition: MWException.php:26
PPNode_DOM\getLength
getLength()
Definition: Preprocessor_DOM.php:1961
PPCustomFrame_DOM\getArgument
getArgument( $index)
Definition: Preprocessor_DOM.php:1878
$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:1931
Preprocessor_DOM\newCustomFrame
newCustomFrame( $args)
Definition: Preprocessor_DOM.php:63
PPNode_DOM\getChildrenOfType
getChildrenOfType( $type)
Definition: Preprocessor_DOM.php:1954
$wgDisableLangConversion
$wgDisableLangConversion
Whether to enable language variant conversion.
Definition: DefaultSettings.php:3111
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:302
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:1047
title
title
Definition: parserTests.txt:239
PPDStack
Stack class to help Preprocessor::preprocessToObj()
Definition: Preprocessor_DOM.php:831
PPNode_DOM\__toString
__toString()
Definition: Preprocessor_DOM.php:1916
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:1870
PPNode_DOM\splitArg
splitArg()
Split a "<part>" node into an associative array containing:
Definition: Preprocessor_DOM.php:1998
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:1938
captcha-old.parser
parser
Definition: captcha-old.py:210
PPCustomFrame_DOM\getArguments
getArguments()
Definition: Preprocessor_DOM.php:1885
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:2020
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:1945
$type
$type
Definition: testCompression.php:48