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