MediaWiki REL1_30
Preprocessor_Hash.php
Go to the documentation of this file.
1<?php
42// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
44 // @codingStandardsIgnoreEnd
45
49 public $parser;
50
51 const CACHE_PREFIX = 'preprocess-hash';
52 const CACHE_VERSION = 2;
53
54 public function __construct( $parser ) {
55 $this->parser = $parser;
56 }
57
61 public function newFrame() {
62 return new PPFrame_Hash( $this );
63 }
64
69 public function newCustomFrame( $args ) {
70 return new PPCustomFrame_Hash( $this, $args );
71 }
72
77 public function newPartNodeArray( $values ) {
78 $list = [];
79
80 foreach ( $values as $k => $val ) {
81 if ( is_int( $k ) ) {
82 $store = [ [ 'part', [
83 [ 'name', [ [ '@index', [ $k ] ] ] ],
84 [ 'value', [ strval( $val ) ] ],
85 ] ] ];
86 } else {
87 $store = [ [ 'part', [
88 [ 'name', [ strval( $k ) ] ],
89 '=',
90 [ 'value', [ strval( $val ) ] ],
91 ] ] ];
92 }
93
94 $list[] = new PPNode_Hash_Tree( $store, 0 );
95 }
96
97 $node = new PPNode_Hash_Array( $list );
98 return $node;
99 }
100
119 public function preprocessToObj( $text, $flags = 0 ) {
121
122 $tree = $this->cacheGetTree( $text, $flags );
123 if ( $tree !== false ) {
124 $store = json_decode( $tree );
125 if ( is_array( $store ) ) {
126 return new PPNode_Hash_Tree( $store, 0 );
127 }
128 }
129
130 $forInclusion = $flags & Parser::PTD_FOR_INCLUSION;
131
132 $xmlishElements = $this->parser->getStripList();
133 $xmlishAllowMissingEndTag = [ 'includeonly', 'noinclude', 'onlyinclude' ];
134 $enableOnlyinclude = false;
135 if ( $forInclusion ) {
136 $ignoredTags = [ 'includeonly', '/includeonly' ];
137 $ignoredElements = [ 'noinclude' ];
138 $xmlishElements[] = 'noinclude';
139 if ( strpos( $text, '<onlyinclude>' ) !== false
140 && strpos( $text, '</onlyinclude>' ) !== false
141 ) {
142 $enableOnlyinclude = true;
143 }
144 } else {
145 $ignoredTags = [ 'noinclude', '/noinclude', 'onlyinclude', '/onlyinclude' ];
146 $ignoredElements = [ 'includeonly' ];
147 $xmlishElements[] = 'includeonly';
148 }
149 $xmlishRegex = implode( '|', array_merge( $xmlishElements, $ignoredTags ) );
150
151 // Use "A" modifier (anchored) instead of "^", because ^ doesn't work with an offset
152 $elementsRegex = "~($xmlishRegex)(?:\s|\/>|>)|(!--)~iA";
153
154 $stack = new PPDStack_Hash;
155
156 $searchBase = "[{<\n";
158 $searchBase .= '-';
159 }
160
161 // For fast reverse searches
162 $revText = strrev( $text );
163 $lengthText = strlen( $text );
164
165 // Input pointer, starts out pointing to a pseudo-newline before the start
166 $i = 0;
167 // Current accumulator. See the doc comment for Preprocessor_Hash for the format.
168 $accum =& $stack->getAccum();
169 // True to find equals signs in arguments
170 $findEquals = false;
171 // True to take notice of pipe characters
172 $findPipe = false;
173 $headingIndex = 1;
174 // True if $i is inside a possible heading
175 $inHeading = false;
176 // True if there are no more greater-than (>) signs right of $i
177 $noMoreGT = false;
178 // Map of tag name => true if there are no more closing tags of given type right of $i
179 $noMoreClosingTag = [];
180 // True to ignore all input up to the next <onlyinclude>
181 $findOnlyinclude = $enableOnlyinclude;
182 // Do a line-start run without outputting an LF character
183 $fakeLineStart = true;
184
185 while ( true ) {
186 // $this->memCheck();
187
188 if ( $findOnlyinclude ) {
189 // Ignore all input up to the next <onlyinclude>
190 $startPos = strpos( $text, '<onlyinclude>', $i );
191 if ( $startPos === false ) {
192 // Ignored section runs to the end
193 $accum[] = [ 'ignore', [ substr( $text, $i ) ] ];
194 break;
195 }
196 $tagEndPos = $startPos + strlen( '<onlyinclude>' ); // past-the-end
197 $accum[] = [ 'ignore', [ substr( $text, $i, $tagEndPos - $i ) ] ];
198 $i = $tagEndPos;
199 $findOnlyinclude = false;
200 }
201
202 if ( $fakeLineStart ) {
203 $found = 'line-start';
204 $curChar = '';
205 } else {
206 # Find next opening brace, closing brace or pipe
207 $search = $searchBase;
208 if ( $stack->top === false ) {
209 $currentClosing = '';
210 } elseif (
211 $stack->top->close === '}-' &&
212 $stack->top->count > 2
213 ) {
214 # adjust closing for -{{{...{{
215 $currentClosing = '}';
216 $search .= $currentClosing;
217 } else {
218 $currentClosing = $stack->top->close;
219 $search .= $currentClosing;
220 }
221 if ( $findPipe ) {
222 $search .= '|';
223 }
224 if ( $findEquals ) {
225 // First equals will be for the template
226 $search .= '=';
227 }
228 $rule = null;
229 # Output literal section, advance input counter
230 $literalLength = strcspn( $text, $search, $i );
231 if ( $literalLength > 0 ) {
232 self::addLiteral( $accum, substr( $text, $i, $literalLength ) );
233 $i += $literalLength;
234 }
235 if ( $i >= $lengthText ) {
236 if ( $currentClosing == "\n" ) {
237 // Do a past-the-end run to finish off the heading
238 $curChar = '';
239 $found = 'line-end';
240 } else {
241 # All done
242 break;
243 }
244 } else {
245 $curChar = $curTwoChar = $text[$i];
246 if ( ( $i + 1 ) < $lengthText ) {
247 $curTwoChar .= $text[$i + 1];
248 }
249 if ( $curChar == '|' ) {
250 $found = 'pipe';
251 } elseif ( $curChar == '=' ) {
252 $found = 'equals';
253 } elseif ( $curChar == '<' ) {
254 $found = 'angle';
255 } elseif ( $curChar == "\n" ) {
256 if ( $inHeading ) {
257 $found = 'line-end';
258 } else {
259 $found = 'line-start';
260 }
261 } elseif ( $curTwoChar == $currentClosing ) {
262 $found = 'close';
263 $curChar = $curTwoChar;
264 } elseif ( $curChar == $currentClosing ) {
265 $found = 'close';
266 } elseif ( isset( $this->rules[$curTwoChar] ) ) {
267 $curChar = $curTwoChar;
268 $found = 'open';
269 $rule = $this->rules[$curChar];
270 } elseif ( isset( $this->rules[$curChar] ) ) {
271 $found = 'open';
272 $rule = $this->rules[$curChar];
273 } else {
274 # Some versions of PHP have a strcspn which stops on
275 # null characters; ignore these and continue.
276 # We also may get '-' and '}' characters here which
277 # don't match -{ or $currentClosing. Add these to
278 # output and continue.
279 if ( $curChar == '-' || $curChar == '}' ) {
280 self::addLiteral( $accum, $curChar );
281 }
282 ++$i;
283 continue;
284 }
285 }
286 }
287
288 if ( $found == 'angle' ) {
289 $matches = false;
290 // Handle </onlyinclude>
291 if ( $enableOnlyinclude
292 && substr( $text, $i, strlen( '</onlyinclude>' ) ) == '</onlyinclude>'
293 ) {
294 $findOnlyinclude = true;
295 continue;
296 }
297
298 // Determine element name
299 if ( !preg_match( $elementsRegex, $text, $matches, 0, $i + 1 ) ) {
300 // Element name missing or not listed
301 self::addLiteral( $accum, '<' );
302 ++$i;
303 continue;
304 }
305 // Handle comments
306 if ( isset( $matches[2] ) && $matches[2] == '!--' ) {
307 // To avoid leaving blank lines, when a sequence of
308 // space-separated comments is both preceded and followed by
309 // a newline (ignoring spaces), then
310 // trim leading and trailing spaces and the trailing newline.
311
312 // Find the end
313 $endPos = strpos( $text, '-->', $i + 4 );
314 if ( $endPos === false ) {
315 // Unclosed comment in input, runs to end
316 $inner = substr( $text, $i );
317 $accum[] = [ 'comment', [ $inner ] ];
318 $i = $lengthText;
319 } else {
320 // Search backwards for leading whitespace
321 $wsStart = $i ? ( $i - strspn( $revText, " \t", $lengthText - $i ) ) : 0;
322
323 // Search forwards for trailing whitespace
324 // $wsEnd will be the position of the last space (or the '>' if there's none)
325 $wsEnd = $endPos + 2 + strspn( $text, " \t", $endPos + 3 );
326
327 // Keep looking forward as long as we're finding more
328 // comments.
329 $comments = [ [ $wsStart, $wsEnd ] ];
330 while ( substr( $text, $wsEnd + 1, 4 ) == '<!--' ) {
331 $c = strpos( $text, '-->', $wsEnd + 4 );
332 if ( $c === false ) {
333 break;
334 }
335 $c = $c + 2 + strspn( $text, " \t", $c + 3 );
336 $comments[] = [ $wsEnd + 1, $c ];
337 $wsEnd = $c;
338 }
339
340 // Eat the line if possible
341 // TODO: This could theoretically be done if $wsStart == 0, i.e. for comments at
342 // the overall start. That's not how Sanitizer::removeHTMLcomments() did it, but
343 // it's a possible beneficial b/c break.
344 if ( $wsStart > 0 && substr( $text, $wsStart - 1, 1 ) == "\n"
345 && substr( $text, $wsEnd + 1, 1 ) == "\n"
346 ) {
347 // Remove leading whitespace from the end of the accumulator
348 $wsLength = $i - $wsStart;
349 $endIndex = count( $accum ) - 1;
350
351 // Sanity check
352 if ( $wsLength > 0
353 && $endIndex >= 0
354 && is_string( $accum[$endIndex] )
355 && strspn( $accum[$endIndex], " \t", -$wsLength ) === $wsLength
356 ) {
357 $accum[$endIndex] = substr( $accum[$endIndex], 0, -$wsLength );
358 }
359
360 // Dump all but the last comment to the accumulator
361 foreach ( $comments as $j => $com ) {
362 $startPos = $com[0];
363 $endPos = $com[1] + 1;
364 if ( $j == ( count( $comments ) - 1 ) ) {
365 break;
366 }
367 $inner = substr( $text, $startPos, $endPos - $startPos );
368 $accum[] = [ 'comment', [ $inner ] ];
369 }
370
371 // Do a line-start run next time to look for headings after the comment
372 $fakeLineStart = true;
373 } else {
374 // No line to eat, just take the comment itself
375 $startPos = $i;
376 $endPos += 2;
377 }
378
379 if ( $stack->top ) {
380 $part = $stack->top->getCurrentPart();
381 if ( !( isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 ) ) {
382 $part->visualEnd = $wsStart;
383 }
384 // Else comments abutting, no change in visual end
385 $part->commentEnd = $endPos;
386 }
387 $i = $endPos + 1;
388 $inner = substr( $text, $startPos, $endPos - $startPos + 1 );
389 $accum[] = [ 'comment', [ $inner ] ];
390 }
391 continue;
392 }
393 $name = $matches[1];
394 $lowerName = strtolower( $name );
395 $attrStart = $i + strlen( $name ) + 1;
396
397 // Find end of tag
398 $tagEndPos = $noMoreGT ? false : strpos( $text, '>', $attrStart );
399 if ( $tagEndPos === false ) {
400 // Infinite backtrack
401 // Disable tag search to prevent worst-case O(N^2) performance
402 $noMoreGT = true;
403 self::addLiteral( $accum, '<' );
404 ++$i;
405 continue;
406 }
407
408 // Handle ignored tags
409 if ( in_array( $lowerName, $ignoredTags ) ) {
410 $accum[] = [ 'ignore', [ substr( $text, $i, $tagEndPos - $i + 1 ) ] ];
411 $i = $tagEndPos + 1;
412 continue;
413 }
414
415 $tagStartPos = $i;
416 if ( $text[$tagEndPos - 1] == '/' ) {
417 // Short end tag
418 $attrEnd = $tagEndPos - 1;
419 $inner = null;
420 $i = $tagEndPos + 1;
421 $close = null;
422 } else {
423 $attrEnd = $tagEndPos;
424 // Find closing tag
425 if (
426 !isset( $noMoreClosingTag[$name] ) &&
427 preg_match( "/<\/" . preg_quote( $name, '/' ) . "\s*>/i",
428 $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 )
429 ) {
430 $inner = substr( $text, $tagEndPos + 1, $matches[0][1] - $tagEndPos - 1 );
431 $i = $matches[0][1] + strlen( $matches[0][0] );
432 $close = $matches[0][0];
433 } else {
434 // No end tag
435 if ( in_array( $name, $xmlishAllowMissingEndTag ) ) {
436 // Let it run out to the end of the text.
437 $inner = substr( $text, $tagEndPos + 1 );
438 $i = $lengthText;
439 $close = null;
440 } else {
441 // Don't match the tag, treat opening tag as literal and resume parsing.
442 $i = $tagEndPos + 1;
443 self::addLiteral( $accum,
444 substr( $text, $tagStartPos, $tagEndPos + 1 - $tagStartPos ) );
445 // Cache results, otherwise we have O(N^2) performance for input like <foo><foo><foo>...
446 $noMoreClosingTag[$name] = true;
447 continue;
448 }
449 }
450 }
451 // <includeonly> and <noinclude> just become <ignore> tags
452 if ( in_array( $lowerName, $ignoredElements ) ) {
453 $accum[] = [ 'ignore', [ substr( $text, $tagStartPos, $i - $tagStartPos ) ] ];
454 continue;
455 }
456
457 if ( $attrEnd <= $attrStart ) {
458 $attr = '';
459 } else {
460 // Note that the attr element contains the whitespace between name and attribute,
461 // this is necessary for precise reconstruction during pre-save transform.
462 $attr = substr( $text, $attrStart, $attrEnd - $attrStart );
463 }
464
465 $children = [
466 [ 'name', [ $name ] ],
467 [ 'attr', [ $attr ] ] ];
468 if ( $inner !== null ) {
469 $children[] = [ 'inner', [ $inner ] ];
470 }
471 if ( $close !== null ) {
472 $children[] = [ 'close', [ $close ] ];
473 }
474 $accum[] = [ 'ext', $children ];
475 } elseif ( $found == 'line-start' ) {
476 // Is this the start of a heading?
477 // Line break belongs before the heading element in any case
478 if ( $fakeLineStart ) {
479 $fakeLineStart = false;
480 } else {
481 self::addLiteral( $accum, $curChar );
482 $i++;
483 }
484
485 $count = strspn( $text, '=', $i, 6 );
486 if ( $count == 1 && $findEquals ) {
487 // DWIM: This looks kind of like a name/value separator.
488 // Let's let the equals handler have it and break the potential
489 // heading. This is heuristic, but AFAICT the methods for
490 // completely correct disambiguation are very complex.
491 } elseif ( $count > 0 ) {
492 $piece = [
493 'open' => "\n",
494 'close' => "\n",
495 'parts' => [ new PPDPart_Hash( str_repeat( '=', $count ) ) ],
496 'startPos' => $i,
497 'count' => $count ];
498 $stack->push( $piece );
499 $accum =& $stack->getAccum();
500 extract( $stack->getFlags() );
501 $i += $count;
502 }
503 } elseif ( $found == 'line-end' ) {
504 $piece = $stack->top;
505 // A heading must be open, otherwise \n wouldn't have been in the search list
506 assert( $piece->open === "\n" );
507 $part = $piece->getCurrentPart();
508 // Search back through the input to see if it has a proper close.
509 // Do this using the reversed string since the other solutions
510 // (end anchor, etc.) are inefficient.
511 $wsLength = strspn( $revText, " \t", $lengthText - $i );
512 $searchStart = $i - $wsLength;
513 if ( isset( $part->commentEnd ) && $searchStart - 1 == $part->commentEnd ) {
514 // Comment found at line end
515 // Search for equals signs before the comment
516 $searchStart = $part->visualEnd;
517 $searchStart -= strspn( $revText, " \t", $lengthText - $searchStart );
518 }
519 $count = $piece->count;
520 $equalsLength = strspn( $revText, '=', $lengthText - $searchStart );
521 if ( $equalsLength > 0 ) {
522 if ( $searchStart - $equalsLength == $piece->startPos ) {
523 // This is just a single string of equals signs on its own line
524 // Replicate the doHeadings behavior /={count}(.+)={count}/
525 // First find out how many equals signs there really are (don't stop at 6)
526 $count = $equalsLength;
527 if ( $count < 3 ) {
528 $count = 0;
529 } else {
530 $count = min( 6, intval( ( $count - 1 ) / 2 ) );
531 }
532 } else {
533 $count = min( $equalsLength, $count );
534 }
535 if ( $count > 0 ) {
536 // Normal match, output <h>
537 $element = [ [ 'possible-h',
538 array_merge(
539 [
540 [ '@level', [ $count ] ],
541 [ '@i', [ $headingIndex++ ] ]
542 ],
543 $accum
544 )
545 ] ];
546 } else {
547 // Single equals sign on its own line, count=0
548 $element = $accum;
549 }
550 } else {
551 // No match, no <h>, just pass down the inner text
552 $element = $accum;
553 }
554 // Unwind the stack
555 $stack->pop();
556 $accum =& $stack->getAccum();
557 extract( $stack->getFlags() );
558
559 // Append the result to the enclosing accumulator
560 array_splice( $accum, count( $accum ), 0, $element );
561
562 // Note that we do NOT increment the input pointer.
563 // This is because the closing linebreak could be the opening linebreak of
564 // another heading. Infinite loops are avoided because the next iteration MUST
565 // hit the heading open case above, which unconditionally increments the
566 // input pointer.
567 } elseif ( $found == 'open' ) {
568 # count opening brace characters
569 $curLen = strlen( $curChar );
570 $count = ( $curLen > 1 ) ?
571 # allow the final character to repeat
572 strspn( $text, $curChar[$curLen - 1], $i + 1 ) + 1 :
573 strspn( $text, $curChar, $i );
574
575 # we need to add to stack only if opening brace count is enough for one of the rules
576 if ( $count >= $rule['min'] ) {
577 # Add it to the stack
578 $piece = [
579 'open' => $curChar,
580 'close' => $rule['end'],
581 'count' => $count,
582 'lineStart' => ( $i > 0 && $text[$i - 1] == "\n" ),
583 ];
584
585 $stack->push( $piece );
586 $accum =& $stack->getAccum();
587 extract( $stack->getFlags() );
588 } else {
589 # Add literal brace(s)
590 self::addLiteral( $accum, str_repeat( $curChar, $count ) );
591 }
592 $i += $count;
593 } elseif ( $found == 'close' ) {
594 $piece = $stack->top;
595 # lets check if there are enough characters for closing brace
596 $maxCount = $piece->count;
597 if ( $piece->close === '}-' && $curChar === '}' ) {
598 $maxCount--; # don't try to match closing '-' as a '}'
599 }
600 $curLen = strlen( $curChar );
601 $count = ( $curLen > 1 ) ? $curLen :
602 strspn( $text, $curChar, $i, $maxCount );
603
604 # check for maximum matching characters (if there are 5 closing
605 # characters, we will probably need only 3 - depending on the rules)
606 $rule = $this->rules[$piece->open];
607 if ( $piece->close === '}-' && $piece->count > 2 ) {
608 # tweak for -{..{{ }}..}-
609 $rule = $this->rules['{'];
610 }
611 if ( $count > $rule['max'] ) {
612 # The specified maximum exists in the callback array, unless the caller
613 # has made an error
614 $matchingCount = $rule['max'];
615 } else {
616 # Count is less than the maximum
617 # Skip any gaps in the callback array to find the true largest match
618 # Need to use array_key_exists not isset because the callback can be null
619 $matchingCount = $count;
620 while ( $matchingCount > 0 && !array_key_exists( $matchingCount, $rule['names'] ) ) {
621 --$matchingCount;
622 }
623 }
624
625 if ( $matchingCount <= 0 ) {
626 # No matching element found in callback array
627 # Output a literal closing brace and continue
628 $endText = substr( $text, $i, $count );
629 self::addLiteral( $accum, $endText );
630 $i += $count;
631 continue;
632 }
633 $name = $rule['names'][$matchingCount];
634 if ( $name === null ) {
635 // No element, just literal text
636 $endText = substr( $text, $i, $matchingCount );
637 $element = $piece->breakSyntax( $matchingCount );
638 self::addLiteral( $element, $endText );
639 } else {
640 # Create XML element
641 $parts = $piece->parts;
642 $titleAccum = $parts[0]->out;
643 unset( $parts[0] );
644
645 $children = [];
646
647 # The invocation is at the start of the line if lineStart is set in
648 # the stack, and all opening brackets are used up.
649 if ( $maxCount == $matchingCount && !empty( $piece->lineStart ) ) {
650 $children[] = [ '@lineStart', [ 1 ] ];
651 }
652 $titleNode = [ 'title', $titleAccum ];
653 $children[] = $titleNode;
654 $argIndex = 1;
655 foreach ( $parts as $part ) {
656 if ( isset( $part->eqpos ) ) {
657 $equalsNode = $part->out[$part->eqpos];
658 $nameNode = [ 'name', array_slice( $part->out, 0, $part->eqpos ) ];
659 $valueNode = [ 'value', array_slice( $part->out, $part->eqpos + 1 ) ];
660 $partNode = [ 'part', [ $nameNode, $equalsNode, $valueNode ] ];
661 $children[] = $partNode;
662 } else {
663 $nameNode = [ 'name', [ [ '@index', [ $argIndex++ ] ] ] ];
664 $valueNode = [ 'value', $part->out ];
665 $partNode = [ 'part', [ $nameNode, $valueNode ] ];
666 $children[] = $partNode;
667 }
668 }
669 $element = [ [ $name, $children ] ];
670 }
671
672 # Advance input pointer
673 $i += $matchingCount;
674
675 # Unwind the stack
676 $stack->pop();
677 $accum =& $stack->getAccum();
678
679 # Re-add the old stack element if it still has unmatched opening characters remaining
680 if ( $matchingCount < $piece->count ) {
681 $piece->parts = [ new PPDPart_Hash ];
682 $piece->count -= $matchingCount;
683 # do we still qualify for any callback with remaining count?
684 $min = $this->rules[$piece->open]['min'];
685 if ( $piece->count >= $min ) {
686 $stack->push( $piece );
687 $accum =& $stack->getAccum();
688 } else {
689 $s = substr( $piece->open, 0, -1 );
690 $s .= str_repeat(
691 substr( $piece->open, -1 ),
692 $piece->count - strlen( $s )
693 );
694 self::addLiteral( $accum, $s );
695 }
696 }
697
698 extract( $stack->getFlags() );
699
700 # Add XML element to the enclosing accumulator
701 array_splice( $accum, count( $accum ), 0, $element );
702 } elseif ( $found == 'pipe' ) {
703 $findEquals = true; // shortcut for getFlags()
704 $stack->addPart();
705 $accum =& $stack->getAccum();
706 ++$i;
707 } elseif ( $found == 'equals' ) {
708 $findEquals = false; // shortcut for getFlags()
709 $accum[] = [ 'equals', [ '=' ] ];
710 $stack->getCurrentPart()->eqpos = count( $accum ) - 1;
711 ++$i;
712 } elseif ( $found == 'dash' ) {
713 self::addLiteral( $accum, '-' );
714 ++$i;
715 }
716 }
717
718 # Output any remaining unclosed brackets
719 foreach ( $stack->stack as $piece ) {
720 array_splice( $stack->rootAccum, count( $stack->rootAccum ), 0, $piece->breakSyntax() );
721 }
722
723 # Enable top-level headings
724 foreach ( $stack->rootAccum as &$node ) {
725 if ( is_array( $node ) && $node[PPNode_Hash_Tree::NAME] === 'possible-h' ) {
726 $node[PPNode_Hash_Tree::NAME] = 'h';
727 }
728 }
729
730 $rootStore = [ [ 'root', $stack->rootAccum ] ];
731 $rootNode = new PPNode_Hash_Tree( $rootStore, 0 );
732
733 // Cache
734 $tree = json_encode( $rootStore, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
735 if ( $tree !== false ) {
736 $this->cacheSetTree( $text, $flags, $tree );
737 }
738
739 return $rootNode;
740 }
741
742 private static function addLiteral( array &$accum, $text ) {
743 $n = count( $accum );
744 if ( $n && is_string( $accum[$n - 1] ) ) {
745 $accum[$n - 1] .= $text;
746 } else {
747 $accum[] = $text;
748 }
749 }
750}
751
756// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
757class PPDStack_Hash extends PPDStack {
758 // @codingStandardsIgnoreEnd
759
760 public function __construct() {
761 $this->elementClass = 'PPDStackElement_Hash';
762 parent::__construct();
763 $this->rootAccum = [];
764 }
765}
766
770// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
771class PPDStackElement_Hash extends PPDStackElement {
772 // @codingStandardsIgnoreEnd
773
774 public function __construct( $data = [] ) {
775 $this->partClass = 'PPDPart_Hash';
776 parent::__construct( $data );
777 }
778
785 public function breakSyntax( $openingCount = false ) {
786 if ( $this->open == "\n" ) {
787 $accum = $this->parts[0]->out;
788 } else {
789 if ( $openingCount === false ) {
790 $openingCount = $this->count;
791 }
792 $s = substr( $this->open, 0, -1 );
793 $s .= str_repeat(
794 substr( $this->open, -1 ),
795 $openingCount - strlen( $s )
796 );
797 $accum = [ $s ];
798 $lastIndex = 0;
799 $first = true;
800 foreach ( $this->parts as $part ) {
801 if ( $first ) {
802 $first = false;
803 } elseif ( is_string( $accum[$lastIndex] ) ) {
804 $accum[$lastIndex] .= '|';
805 } else {
806 $accum[++$lastIndex] = '|';
807 }
808 foreach ( $part->out as $node ) {
809 if ( is_string( $node ) && is_string( $accum[$lastIndex] ) ) {
810 $accum[$lastIndex] .= $node;
811 } else {
812 $accum[++$lastIndex] = $node;
813 }
814 }
815 }
816 }
817 return $accum;
818 }
819}
820
824// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
825class PPDPart_Hash extends PPDPart {
826 // @codingStandardsIgnoreEnd
827
828 public function __construct( $out = '' ) {
829 if ( $out !== '' ) {
830 $accum = [ $out ];
831 } else {
832 $accum = [];
833 }
834 parent::__construct( $accum );
835 }
836}
837
842// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
843class PPFrame_Hash implements PPFrame {
844 // @codingStandardsIgnoreEnd
845
849 public $parser;
850
854 public $preprocessor;
855
859 public $title;
860 public $titleCache;
861
866 public $loopCheckHash;
867
872 public $depth;
873
874 private $volatile = false;
875 private $ttl = null;
876
880 protected $childExpansionCache;
881
886 public function __construct( $preprocessor ) {
887 $this->preprocessor = $preprocessor;
888 $this->parser = $preprocessor->parser;
889 $this->title = $this->parser->mTitle;
890 $this->titleCache = [ $this->title ? $this->title->getPrefixedDBkey() : false ];
891 $this->loopCheckHash = [];
892 $this->depth = 0;
893 $this->childExpansionCache = [];
894 }
895
906 public function newChild( $args = false, $title = false, $indexOffset = 0 ) {
907 $namedArgs = [];
908 $numberedArgs = [];
909 if ( $title === false ) {
910 $title = $this->title;
911 }
912 if ( $args !== false ) {
913 if ( $args instanceof PPNode_Hash_Array ) {
914 $args = $args->value;
915 } elseif ( !is_array( $args ) ) {
916 throw new MWException( __METHOD__ . ': $args must be array or PPNode_Hash_Array' );
917 }
918 foreach ( $args as $arg ) {
919 $bits = $arg->splitArg();
920 if ( $bits['index'] !== '' ) {
921 // Numbered parameter
922 $index = $bits['index'] - $indexOffset;
923 if ( isset( $namedArgs[$index] ) || isset( $numberedArgs[$index] ) ) {
924 $this->parser->getOutput()->addWarning( wfMessage( 'duplicate-args-warning',
925 wfEscapeWikiText( $this->title ),
926 wfEscapeWikiText( $title ),
927 wfEscapeWikiText( $index ) )->text() );
928 $this->parser->addTrackingCategory( 'duplicate-args-category' );
929 }
930 $numberedArgs[$index] = $bits['value'];
931 unset( $namedArgs[$index] );
932 } else {
933 // Named parameter
934 $name = trim( $this->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
935 if ( isset( $namedArgs[$name] ) || isset( $numberedArgs[$name] ) ) {
936 $this->parser->getOutput()->addWarning( wfMessage( 'duplicate-args-warning',
937 wfEscapeWikiText( $this->title ),
938 wfEscapeWikiText( $title ),
939 wfEscapeWikiText( $name ) )->text() );
940 $this->parser->addTrackingCategory( 'duplicate-args-category' );
941 }
942 $namedArgs[$name] = $bits['value'];
943 unset( $numberedArgs[$name] );
944 }
945 }
946 }
947 return new PPTemplateFrame_Hash( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title );
948 }
949
957 public function cachedExpand( $key, $root, $flags = 0 ) {
958 // we don't have a parent, so we don't have a cache
959 return $this->expand( $root, $flags );
960 }
961
968 public function expand( $root, $flags = 0 ) {
969 static $expansionDepth = 0;
970 if ( is_string( $root ) ) {
971 return $root;
972 }
973
974 if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount() ) {
975 $this->parser->limitationWarn( 'node-count-exceeded',
976 $this->parser->mPPNodeCount,
977 $this->parser->mOptions->getMaxPPNodeCount()
978 );
979 return '<span class="error">Node-count limit exceeded</span>';
980 }
981 if ( $expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth() ) {
982 $this->parser->limitationWarn( 'expansion-depth-exceeded',
983 $expansionDepth,
984 $this->parser->mOptions->getMaxPPExpandDepth()
985 );
986 return '<span class="error">Expansion depth limit exceeded</span>';
987 }
988 ++$expansionDepth;
989 if ( $expansionDepth > $this->parser->mHighestExpansionDepth ) {
990 $this->parser->mHighestExpansionDepth = $expansionDepth;
991 }
992
993 $outStack = [ '', '' ];
994 $iteratorStack = [ false, $root ];
995 $indexStack = [ 0, 0 ];
996
997 while ( count( $iteratorStack ) > 1 ) {
998 $level = count( $outStack ) - 1;
999 $iteratorNode =& $iteratorStack[$level];
1000 $out =& $outStack[$level];
1001 $index =& $indexStack[$level];
1002
1003 if ( is_array( $iteratorNode ) ) {
1004 if ( $index >= count( $iteratorNode ) ) {
1005 // All done with this iterator
1006 $iteratorStack[$level] = false;
1007 $contextNode = false;
1008 } else {
1009 $contextNode = $iteratorNode[$index];
1010 $index++;
1011 }
1012 } elseif ( $iteratorNode instanceof PPNode_Hash_Array ) {
1013 if ( $index >= $iteratorNode->getLength() ) {
1014 // All done with this iterator
1015 $iteratorStack[$level] = false;
1016 $contextNode = false;
1017 } else {
1018 $contextNode = $iteratorNode->item( $index );
1019 $index++;
1020 }
1021 } else {
1022 // Copy to $contextNode and then delete from iterator stack,
1023 // because this is not an iterator but we do have to execute it once
1024 $contextNode = $iteratorStack[$level];
1025 $iteratorStack[$level] = false;
1026 }
1027
1028 $newIterator = false;
1029 $contextName = false;
1030 $contextChildren = false;
1031
1032 if ( $contextNode === false ) {
1033 // nothing to do
1034 } elseif ( is_string( $contextNode ) ) {
1035 $out .= $contextNode;
1036 } elseif ( $contextNode instanceof PPNode_Hash_Array ) {
1037 $newIterator = $contextNode;
1038 } elseif ( $contextNode instanceof PPNode_Hash_Attr ) {
1039 // No output
1040 } elseif ( $contextNode instanceof PPNode_Hash_Text ) {
1041 $out .= $contextNode->value;
1042 } elseif ( $contextNode instanceof PPNode_Hash_Tree ) {
1043 $contextName = $contextNode->name;
1044 $contextChildren = $contextNode->getRawChildren();
1045 } elseif ( is_array( $contextNode ) ) {
1046 // Node descriptor array
1047 if ( count( $contextNode ) !== 2 ) {
1048 throw new MWException( __METHOD__.
1049 ': found an array where a node descriptor should be' );
1050 }
1051 list( $contextName, $contextChildren ) = $contextNode;
1052 } else {
1053 throw new MWException( __METHOD__ . ': Invalid parameter type' );
1054 }
1055
1056 // Handle node descriptor array or tree object
1057 if ( $contextName === false ) {
1058 // Not a node, already handled above
1059 } elseif ( $contextName[0] === '@' ) {
1060 // Attribute: no output
1061 } elseif ( $contextName === 'template' ) {
1062 # Double-brace expansion
1063 $bits = PPNode_Hash_Tree::splitRawTemplate( $contextChildren );
1064 if ( $flags & PPFrame::NO_TEMPLATES ) {
1065 $newIterator = $this->virtualBracketedImplode(
1066 '{{', '|', '}}',
1067 $bits['title'],
1068 $bits['parts']
1069 );
1070 } else {
1071 $ret = $this->parser->braceSubstitution( $bits, $this );
1072 if ( isset( $ret['object'] ) ) {
1073 $newIterator = $ret['object'];
1074 } else {
1075 $out .= $ret['text'];
1076 }
1077 }
1078 } elseif ( $contextName === 'tplarg' ) {
1079 # Triple-brace expansion
1080 $bits = PPNode_Hash_Tree::splitRawTemplate( $contextChildren );
1081 if ( $flags & PPFrame::NO_ARGS ) {
1082 $newIterator = $this->virtualBracketedImplode(
1083 '{{{', '|', '}}}',
1084 $bits['title'],
1085 $bits['parts']
1086 );
1087 } else {
1088 $ret = $this->parser->argSubstitution( $bits, $this );
1089 if ( isset( $ret['object'] ) ) {
1090 $newIterator = $ret['object'];
1091 } else {
1092 $out .= $ret['text'];
1093 }
1094 }
1095 } elseif ( $contextName === 'comment' ) {
1096 # HTML-style comment
1097 # Remove it in HTML, pre+remove and STRIP_COMMENTS modes
1098 # Not in RECOVER_COMMENTS mode (msgnw) though.
1099 if ( ( $this->parser->ot['html']
1100 || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() )
1101 || ( $flags & PPFrame::STRIP_COMMENTS )
1102 ) && !( $flags & PPFrame::RECOVER_COMMENTS )
1103 ) {
1104 $out .= '';
1105 } elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) {
1106 # Add a strip marker in PST mode so that pstPass2() can
1107 # run some old-fashioned regexes on the result.
1108 # Not in RECOVER_COMMENTS mode (extractSections) though.
1109 $out .= $this->parser->insertStripItem( $contextChildren[0] );
1110 } else {
1111 # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
1112 $out .= $contextChildren[0];
1113 }
1114 } elseif ( $contextName === 'ignore' ) {
1115 # Output suppression used by <includeonly> etc.
1116 # OT_WIKI will only respect <ignore> in substed templates.
1117 # The other output types respect it unless NO_IGNORE is set.
1118 # extractSections() sets NO_IGNORE and so never respects it.
1119 if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] )
1120 || ( $flags & PPFrame::NO_IGNORE )
1121 ) {
1122 $out .= $contextChildren[0];
1123 } else {
1124 // $out .= '';
1125 }
1126 } elseif ( $contextName === 'ext' ) {
1127 # Extension tag
1128 $bits = PPNode_Hash_Tree::splitRawExt( $contextChildren ) +
1129 [ 'attr' => null, 'inner' => null, 'close' => null ];
1130 if ( $flags & PPFrame::NO_TAGS ) {
1131 $s = '<' . $bits['name']->getFirstChild()->value;
1132 if ( $bits['attr'] ) {
1133 $s .= $bits['attr']->getFirstChild()->value;
1134 }
1135 if ( $bits['inner'] ) {
1136 $s .= '>' . $bits['inner']->getFirstChild()->value;
1137 if ( $bits['close'] ) {
1138 $s .= $bits['close']->getFirstChild()->value;
1139 }
1140 } else {
1141 $s .= '/>';
1142 }
1143 $out .= $s;
1144 } else {
1145 $out .= $this->parser->extensionSubstitution( $bits, $this );
1146 }
1147 } elseif ( $contextName === 'h' ) {
1148 # Heading
1149 if ( $this->parser->ot['html'] ) {
1150 # Expand immediately and insert heading index marker
1151 $s = $this->expand( $contextChildren, $flags );
1152 $bits = PPNode_Hash_Tree::splitRawHeading( $contextChildren );
1153 $titleText = $this->title->getPrefixedDBkey();
1154 $this->parser->mHeadings[] = [ $titleText, $bits['i'] ];
1155 $serial = count( $this->parser->mHeadings ) - 1;
1156 $marker = Parser::MARKER_PREFIX . "-h-$serial-" . Parser::MARKER_SUFFIX;
1157 $s = substr( $s, 0, $bits['level'] ) . $marker . substr( $s, $bits['level'] );
1158 $this->parser->mStripState->addGeneral( $marker, '' );
1159 $out .= $s;
1160 } else {
1161 # Expand in virtual stack
1162 $newIterator = $contextChildren;
1163 }
1164 } else {
1165 # Generic recursive expansion
1166 $newIterator = $contextChildren;
1167 }
1168
1169 if ( $newIterator !== false ) {
1170 $outStack[] = '';
1171 $iteratorStack[] = $newIterator;
1172 $indexStack[] = 0;
1173 } elseif ( $iteratorStack[$level] === false ) {
1174 // Return accumulated value to parent
1175 // With tail recursion
1176 while ( $iteratorStack[$level] === false && $level > 0 ) {
1177 $outStack[$level - 1] .= $out;
1178 array_pop( $outStack );
1179 array_pop( $iteratorStack );
1180 array_pop( $indexStack );
1181 $level--;
1182 }
1183 }
1184 }
1185 --$expansionDepth;
1186 return $outStack[0];
1187 }
1188
1195 public function implodeWithFlags( $sep, $flags /*, ... */ ) {
1196 $args = array_slice( func_get_args(), 2 );
1197
1198 $first = true;
1199 $s = '';
1200 foreach ( $args as $root ) {
1201 if ( $root instanceof PPNode_Hash_Array ) {
1202 $root = $root->value;
1203 }
1204 if ( !is_array( $root ) ) {
1205 $root = [ $root ];
1206 }
1207 foreach ( $root as $node ) {
1208 if ( $first ) {
1209 $first = false;
1210 } else {
1211 $s .= $sep;
1212 }
1213 $s .= $this->expand( $node, $flags );
1214 }
1215 }
1216 return $s;
1217 }
1218
1226 public function implode( $sep /*, ... */ ) {
1227 $args = array_slice( func_get_args(), 1 );
1228
1229 $first = true;
1230 $s = '';
1231 foreach ( $args as $root ) {
1232 if ( $root instanceof PPNode_Hash_Array ) {
1233 $root = $root->value;
1234 }
1235 if ( !is_array( $root ) ) {
1236 $root = [ $root ];
1237 }
1238 foreach ( $root as $node ) {
1239 if ( $first ) {
1240 $first = false;
1241 } else {
1242 $s .= $sep;
1243 }
1244 $s .= $this->expand( $node );
1245 }
1246 }
1247 return $s;
1248 }
1249
1258 public function virtualImplode( $sep /*, ... */ ) {
1259 $args = array_slice( func_get_args(), 1 );
1260 $out = [];
1261 $first = true;
1262
1263 foreach ( $args as $root ) {
1264 if ( $root instanceof PPNode_Hash_Array ) {
1265 $root = $root->value;
1266 }
1267 if ( !is_array( $root ) ) {
1268 $root = [ $root ];
1269 }
1270 foreach ( $root as $node ) {
1271 if ( $first ) {
1272 $first = false;
1273 } else {
1274 $out[] = $sep;
1275 }
1276 $out[] = $node;
1277 }
1278 }
1279 return new PPNode_Hash_Array( $out );
1280 }
1281
1291 public function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) {
1292 $args = array_slice( func_get_args(), 3 );
1293 $out = [ $start ];
1294 $first = true;
1295
1296 foreach ( $args as $root ) {
1297 if ( $root instanceof PPNode_Hash_Array ) {
1298 $root = $root->value;
1299 }
1300 if ( !is_array( $root ) ) {
1301 $root = [ $root ];
1302 }
1303 foreach ( $root as $node ) {
1304 if ( $first ) {
1305 $first = false;
1306 } else {
1307 $out[] = $sep;
1308 }
1309 $out[] = $node;
1310 }
1311 }
1312 $out[] = $end;
1313 return new PPNode_Hash_Array( $out );
1314 }
1315
1316 public function __toString() {
1317 return 'frame{}';
1318 }
1319
1324 public function getPDBK( $level = false ) {
1325 if ( $level === false ) {
1326 return $this->title->getPrefixedDBkey();
1327 } else {
1328 return isset( $this->titleCache[$level] ) ? $this->titleCache[$level] : false;
1329 }
1330 }
1331
1335 public function getArguments() {
1336 return [];
1337 }
1338
1342 public function getNumberedArguments() {
1343 return [];
1344 }
1345
1349 public function getNamedArguments() {
1350 return [];
1351 }
1352
1358 public function isEmpty() {
1359 return true;
1360 }
1361
1366 public function getArgument( $name ) {
1367 return false;
1368 }
1369
1377 public function loopCheck( $title ) {
1378 return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] );
1379 }
1380
1386 public function isTemplate() {
1387 return false;
1388 }
1389
1395 public function getTitle() {
1396 return $this->title;
1397 }
1398
1404 public function setVolatile( $flag = true ) {
1405 $this->volatile = $flag;
1406 }
1407
1413 public function isVolatile() {
1414 return $this->volatile;
1415 }
1416
1422 public function setTTL( $ttl ) {
1423 if ( $ttl !== null && ( $this->ttl === null || $ttl < $this->ttl ) ) {
1424 $this->ttl = $ttl;
1425 }
1426 }
1427
1433 public function getTTL() {
1434 return $this->ttl;
1435 }
1436}
1437
1442// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
1443class PPTemplateFrame_Hash extends PPFrame_Hash {
1444 // @codingStandardsIgnoreEnd
1445
1446 public $numberedArgs, $namedArgs, $parent;
1447 public $numberedExpansionCache, $namedExpansionCache;
1448
1456 public function __construct( $preprocessor, $parent = false, $numberedArgs = [],
1457 $namedArgs = [], $title = false
1458 ) {
1459 parent::__construct( $preprocessor );
1460
1461 $this->parent = $parent;
1462 $this->numberedArgs = $numberedArgs;
1463 $this->namedArgs = $namedArgs;
1464 $this->title = $title;
1465 $pdbk = $title ? $title->getPrefixedDBkey() : false;
1466 $this->titleCache = $parent->titleCache;
1467 $this->titleCache[] = $pdbk;
1468 $this->loopCheckHash = /*clone*/ $parent->loopCheckHash;
1469 if ( $pdbk !== false ) {
1470 $this->loopCheckHash[$pdbk] = true;
1471 }
1472 $this->depth = $parent->depth + 1;
1473 $this->numberedExpansionCache = $this->namedExpansionCache = [];
1474 }
1475
1476 public function __toString() {
1477 $s = 'tplframe{';
1478 $first = true;
1479 $args = $this->numberedArgs + $this->namedArgs;
1480 foreach ( $args as $name => $value ) {
1481 if ( $first ) {
1482 $first = false;
1483 } else {
1484 $s .= ', ';
1485 }
1486 $s .= "\"$name\":\"" .
1487 str_replace( '"', '\\"', $value->__toString() ) . '"';
1488 }
1489 $s .= '}';
1490 return $s;
1491 }
1492
1500 public function cachedExpand( $key, $root, $flags = 0 ) {
1501 if ( isset( $this->parent->childExpansionCache[$key] ) ) {
1502 return $this->parent->childExpansionCache[$key];
1503 }
1504 $retval = $this->expand( $root, $flags );
1505 if ( !$this->isVolatile() ) {
1506 $this->parent->childExpansionCache[$key] = $retval;
1507 }
1508 return $retval;
1509 }
1510
1516 public function isEmpty() {
1517 return !count( $this->numberedArgs ) && !count( $this->namedArgs );
1518 }
1519
1523 public function getArguments() {
1524 $arguments = [];
1525 foreach ( array_merge(
1526 array_keys( $this->numberedArgs ),
1527 array_keys( $this->namedArgs ) ) as $key ) {
1528 $arguments[$key] = $this->getArgument( $key );
1529 }
1530 return $arguments;
1531 }
1532
1536 public function getNumberedArguments() {
1537 $arguments = [];
1538 foreach ( array_keys( $this->numberedArgs ) as $key ) {
1539 $arguments[$key] = $this->getArgument( $key );
1540 }
1541 return $arguments;
1542 }
1543
1547 public function getNamedArguments() {
1548 $arguments = [];
1549 foreach ( array_keys( $this->namedArgs ) as $key ) {
1550 $arguments[$key] = $this->getArgument( $key );
1551 }
1552 return $arguments;
1553 }
1554
1559 public function getNumberedArgument( $index ) {
1560 if ( !isset( $this->numberedArgs[$index] ) ) {
1561 return false;
1562 }
1563 if ( !isset( $this->numberedExpansionCache[$index] ) ) {
1564 # No trimming for unnamed arguments
1565 $this->numberedExpansionCache[$index] = $this->parent->expand(
1566 $this->numberedArgs[$index],
1567 PPFrame::STRIP_COMMENTS
1568 );
1569 }
1570 return $this->numberedExpansionCache[$index];
1571 }
1572
1577 public function getNamedArgument( $name ) {
1578 if ( !isset( $this->namedArgs[$name] ) ) {
1579 return false;
1580 }
1581 if ( !isset( $this->namedExpansionCache[$name] ) ) {
1582 # Trim named arguments post-expand, for backwards compatibility
1583 $this->namedExpansionCache[$name] = trim(
1584 $this->parent->expand( $this->namedArgs[$name], PPFrame::STRIP_COMMENTS ) );
1585 }
1586 return $this->namedExpansionCache[$name];
1587 }
1588
1593 public function getArgument( $name ) {
1594 $text = $this->getNumberedArgument( $name );
1595 if ( $text === false ) {
1596 $text = $this->getNamedArgument( $name );
1597 }
1598 return $text;
1599 }
1600
1606 public function isTemplate() {
1607 return true;
1608 }
1609
1610 public function setVolatile( $flag = true ) {
1611 parent::setVolatile( $flag );
1612 $this->parent->setVolatile( $flag );
1613 }
1614
1615 public function setTTL( $ttl ) {
1616 parent::setTTL( $ttl );
1617 $this->parent->setTTL( $ttl );
1618 }
1619}
1620
1625// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
1626class PPCustomFrame_Hash extends PPFrame_Hash {
1627 // @codingStandardsIgnoreEnd
1628
1629 public $args;
1630
1631 public function __construct( $preprocessor, $args ) {
1632 parent::__construct( $preprocessor );
1633 $this->args = $args;
1634 }
1635
1636 public function __toString() {
1637 $s = 'cstmframe{';
1638 $first = true;
1639 foreach ( $this->args as $name => $value ) {
1640 if ( $first ) {
1641 $first = false;
1642 } else {
1643 $s .= ', ';
1644 }
1645 $s .= "\"$name\":\"" .
1646 str_replace( '"', '\\"', $value->__toString() ) . '"';
1647 }
1648 $s .= '}';
1649 return $s;
1650 }
1651
1655 public function isEmpty() {
1656 return !count( $this->args );
1657 }
1658
1663 public function getArgument( $index ) {
1664 if ( !isset( $this->args[$index] ) ) {
1665 return false;
1666 }
1667 return $this->args[$index];
1668 }
1669
1670 public function getArguments() {
1671 return $this->args;
1672 }
1673}
1674
1678// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
1679class PPNode_Hash_Tree implements PPNode {
1680 // @codingStandardsIgnoreEnd
1681
1682 public $name;
1683
1690
1694 private $store;
1695
1699 private $index;
1700
1705 const NAME = 0;
1706
1711 const CHILDREN = 1;
1712
1720 public function __construct( array $store, $index ) {
1721 $this->store = $store;
1722 $this->index = $index;
1723 list( $this->name, $this->rawChildren ) = $this->store[$index];
1724 }
1725
1734 public static function factory( array $store, $index ) {
1735 if ( !isset( $store[$index] ) ) {
1736 return false;
1737 }
1738
1739 $descriptor = $store[$index];
1740 if ( is_string( $descriptor ) ) {
1741 $class = 'PPNode_Hash_Text';
1742 } elseif ( is_array( $descriptor ) ) {
1743 if ( $descriptor[self::NAME][0] === '@' ) {
1744 $class = 'PPNode_Hash_Attr';
1745 } else {
1746 $class = 'PPNode_Hash_Tree';
1747 }
1748 } else {
1749 throw new MWException( __METHOD__.': invalid node descriptor' );
1750 }
1751 return new $class( $store, $index );
1752 }
1753
1757 public function __toString() {
1758 $inner = '';
1759 $attribs = '';
1760 for ( $node = $this->getFirstChild(); $node; $node = $node->getNextSibling() ) {
1761 if ( $node instanceof PPNode_Hash_Attr ) {
1762 $attribs .= ' ' . $node->name . '="' . htmlspecialchars( $node->value ) . '"';
1763 } else {
1764 $inner .= $node->__toString();
1765 }
1766 }
1767 if ( $inner === '' ) {
1768 return "<{$this->name}$attribs/>";
1769 } else {
1770 return "<{$this->name}$attribs>$inner</{$this->name}>";
1771 }
1772 }
1773
1777 public function getChildren() {
1778 $children = [];
1779 foreach ( $this->rawChildren as $i => $child ) {
1780 $children[] = self::factory( $this->rawChildren, $i );
1781 }
1782 return new PPNode_Hash_Array( $children );
1783 }
1784
1792 public function getFirstChild() {
1793 if ( !isset( $this->rawChildren[0] ) ) {
1794 return false;
1795 } else {
1796 return self::factory( $this->rawChildren, 0 );
1797 }
1798 }
1799
1807 public function getNextSibling() {
1808 return self::factory( $this->store, $this->index + 1 );
1809 }
1810
1817 public function getChildrenOfType( $name ) {
1818 $children = [];
1819 foreach ( $this->rawChildren as $i => $child ) {
1820 if ( is_array( $child ) && $child[self::NAME] === $name ) {
1821 $children[] = self::factory( $this->rawChildren, $i );
1822 }
1823 }
1824 return new PPNode_Hash_Array( $children );
1825 }
1826
1831 public function getRawChildren() {
1832 return $this->rawChildren;
1833 }
1834
1838 public function getLength() {
1839 return false;
1840 }
1841
1846 public function item( $i ) {
1847 return false;
1848 }
1849
1853 public function getName() {
1854 return $this->name;
1855 }
1856
1866 public function splitArg() {
1867 return self::splitRawArg( $this->rawChildren );
1868 }
1869
1875 public static function splitRawArg( array $children ) {
1876 $bits = [];
1877 foreach ( $children as $i => $child ) {
1878 if ( !is_array( $child ) ) {
1879 continue;
1880 }
1881 if ( $child[self::NAME] === 'name' ) {
1882 $bits['name'] = new self( $children, $i );
1883 if ( isset( $child[self::CHILDREN][0][self::NAME] )
1884 && $child[self::CHILDREN][0][self::NAME] === '@index'
1885 ) {
1886 $bits['index'] = $child[self::CHILDREN][0][self::CHILDREN][0];
1887 }
1888 } elseif ( $child[self::NAME] === 'value' ) {
1889 $bits['value'] = new self( $children, $i );
1890 }
1891 }
1892
1893 if ( !isset( $bits['name'] ) ) {
1894 throw new MWException( 'Invalid brace node passed to ' . __METHOD__ );
1895 }
1896 if ( !isset( $bits['index'] ) ) {
1897 $bits['index'] = '';
1898 }
1899 return $bits;
1900 }
1901
1909 public function splitExt() {
1910 return self::splitRawExt( $this->rawChildren );
1911 }
1912
1918 public static function splitRawExt( array $children ) {
1919 $bits = [];
1920 foreach ( $children as $i => $child ) {
1921 if ( !is_array( $child ) ) {
1922 continue;
1923 }
1924 switch ( $child[self::NAME] ) {
1925 case 'name':
1926 $bits['name'] = new self( $children, $i );
1927 break;
1928 case 'attr':
1929 $bits['attr'] = new self( $children, $i );
1930 break;
1931 case 'inner':
1932 $bits['inner'] = new self( $children, $i );
1933 break;
1934 case 'close':
1935 $bits['close'] = new self( $children, $i );
1936 break;
1937 }
1938 }
1939 if ( !isset( $bits['name'] ) ) {
1940 throw new MWException( 'Invalid ext node passed to ' . __METHOD__ );
1941 }
1942 return $bits;
1943 }
1944
1951 public function splitHeading() {
1952 if ( $this->name !== 'h' ) {
1953 throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
1954 }
1955 return self::splitRawHeading( $this->rawChildren );
1956 }
1957
1963 public static function splitRawHeading( array $children ) {
1964 $bits = [];
1965 foreach ( $children as $i => $child ) {
1966 if ( !is_array( $child ) ) {
1967 continue;
1968 }
1969 if ( $child[self::NAME] === '@i' ) {
1970 $bits['i'] = $child[self::CHILDREN][0];
1971 } elseif ( $child[self::NAME] === '@level' ) {
1972 $bits['level'] = $child[self::CHILDREN][0];
1973 }
1974 }
1975 if ( !isset( $bits['i'] ) ) {
1976 throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
1977 }
1978 return $bits;
1979 }
1980
1987 public function splitTemplate() {
1988 return self::splitRawTemplate( $this->rawChildren );
1989 }
1990
1996 public static function splitRawTemplate( array $children ) {
1997 $parts = [];
1998 $bits = [ 'lineStart' => '' ];
1999 foreach ( $children as $i => $child ) {
2000 if ( !is_array( $child ) ) {
2001 continue;
2002 }
2003 switch ( $child[self::NAME] ) {
2004 case 'title':
2005 $bits['title'] = new self( $children, $i );
2006 break;
2007 case 'part':
2008 $parts[] = new self( $children, $i );
2009 break;
2010 case '@lineStart':
2011 $bits['lineStart'] = '1';
2012 break;
2013 }
2014 }
2015 if ( !isset( $bits['title'] ) ) {
2016 throw new MWException( 'Invalid node passed to ' . __METHOD__ );
2017 }
2018 $bits['parts'] = new PPNode_Hash_Array( $parts );
2019 return $bits;
2020 }
2021}
2022
2026// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
2027class PPNode_Hash_Text implements PPNode {
2028 // @codingStandardsIgnoreEnd
2029
2030 public $value;
2031 private $store, $index;
2032
2040 public function __construct( array $store, $index ) {
2041 $this->value = $store[$index];
2042 if ( !is_scalar( $this->value ) ) {
2043 throw new MWException( __CLASS__ . ' given object instead of string' );
2044 }
2045 $this->store = $store;
2046 $this->index = $index;
2047 }
2048
2049 public function __toString() {
2050 return htmlspecialchars( $this->value );
2051 }
2052
2053 public function getNextSibling() {
2054 return PPNode_Hash_Tree::factory( $this->store, $this->index + 1 );
2055 }
2056
2057 public function getChildren() {
2058 return false;
2059 }
2060
2061 public function getFirstChild() {
2062 return false;
2063 }
2064
2065 public function getChildrenOfType( $name ) {
2066 return false;
2067 }
2068
2069 public function getLength() {
2070 return false;
2071 }
2072
2073 public function item( $i ) {
2074 return false;
2075 }
2076
2077 public function getName() {
2078 return '#text';
2079 }
2080
2081 public function splitArg() {
2082 throw new MWException( __METHOD__ . ': not supported' );
2083 }
2084
2085 public function splitExt() {
2086 throw new MWException( __METHOD__ . ': not supported' );
2087 }
2088
2089 public function splitHeading() {
2090 throw new MWException( __METHOD__ . ': not supported' );
2091 }
2092}
2093
2097// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
2098class PPNode_Hash_Array implements PPNode {
2099 // @codingStandardsIgnoreEnd
2100
2101 public $value;
2102
2103 public function __construct( $value ) {
2104 $this->value = $value;
2105 }
2106
2107 public function __toString() {
2108 return var_export( $this, true );
2109 }
2110
2111 public function getLength() {
2112 return count( $this->value );
2113 }
2114
2115 public function item( $i ) {
2116 return $this->value[$i];
2117 }
2118
2119 public function getName() {
2120 return '#nodelist';
2121 }
2122
2123 public function getNextSibling() {
2124 return false;
2125 }
2126
2127 public function getChildren() {
2128 return false;
2129 }
2130
2131 public function getFirstChild() {
2132 return false;
2133 }
2134
2135 public function getChildrenOfType( $name ) {
2136 return false;
2137 }
2138
2139 public function splitArg() {
2140 throw new MWException( __METHOD__ . ': not supported' );
2141 }
2142
2143 public function splitExt() {
2144 throw new MWException( __METHOD__ . ': not supported' );
2145 }
2146
2147 public function splitHeading() {
2148 throw new MWException( __METHOD__ . ': not supported' );
2149 }
2150}
2151
2155// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
2156class PPNode_Hash_Attr implements PPNode {
2157 // @codingStandardsIgnoreEnd
2158
2159 public $name, $value;
2160 private $store, $index;
2161
2169 public function __construct( array $store, $index ) {
2170 $descriptor = $store[$index];
2171 if ( $descriptor[PPNode_Hash_Tree::NAME][0] !== '@' ) {
2172 throw new MWException( __METHOD__.': invalid name in attribute descriptor' );
2173 }
2174 $this->name = substr( $descriptor[PPNode_Hash_Tree::NAME], 1 );
2175 $this->value = $descriptor[PPNode_Hash_Tree::CHILDREN][0];
2176 $this->store = $store;
2177 $this->index = $index;
2178 }
2179
2180 public function __toString() {
2181 return "<@{$this->name}>" . htmlspecialchars( $this->value ) . "</@{$this->name}>";
2182 }
2183
2184 public function getName() {
2185 return $this->name;
2186 }
2187
2188 public function getNextSibling() {
2189 return PPNode_Hash_Tree::factory( $this->store, $this->index + 1 );
2190 }
2191
2192 public function getChildren() {
2193 return false;
2194 }
2195
2196 public function getFirstChild() {
2197 return false;
2198 }
2199
2200 public function getChildrenOfType( $name ) {
2201 return false;
2202 }
2203
2204 public function getLength() {
2205 return false;
2206 }
2207
2208 public function item( $i ) {
2209 return false;
2210 }
2211
2212 public function splitArg() {
2213 throw new MWException( __METHOD__ . ': not supported' );
2214 }
2215
2216 public function splitExt() {
2217 throw new MWException( __METHOD__ . ': not supported' );
2218 }
2219
2220 public function splitHeading() {
2221 throw new MWException( __METHOD__ . ': not supported' );
2222 }
2223}
$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()
splitArg()
Split a "<part>" node into an associative array containing: name PPNode name index String index value...
getName()
Get the name of this node.
getNextSibling()
Get the next sibling of any node.
getLength()
Returns the length of the array, or false if this is not an array-type node.
splitHeading()
Split an "<h>" node.
getChildrenOfType( $name)
Get all children of this tree node which have a given name.
getFirstChild()
Get the first child of a tree node.
getChildren()
Get an array-type node containing the children of this node.
item( $i)
Returns an item of an array-type node.
splitExt()
Split an "<ext>" node into an associative array containing name, attr, inner and close All values in ...
getChildren()
Get an array-type node containing the children of this node.
splitExt()
Split an "<ext>" node into an associative array containing name, attr, inner and close All values in ...
splitArg()
Split a "<part>" node into an associative array containing: name PPNode name index String index value...
__construct(array $store, $index)
Construct an object using the data from $store[$index].
item( $i)
Returns an item of an array-type node.
getChildrenOfType( $name)
Get all children of this tree node which have a given name.
splitHeading()
Split an "<h>" node.
getName()
Get the name of this node.
getLength()
Returns the length of the array, or false if this is not an array-type node.
getFirstChild()
Get the first child of a tree node.
getNextSibling()
Get the next sibling of any node.
getChildren()
Get an array-type node containing the children of this node.
getChildrenOfType( $name)
Get all children of this tree node which have a given name.
__construct(array $store, $index)
Construct an object using the data from $store[$index].
getLength()
Returns the length of the array, or false if this is not an array-type node.
splitHeading()
Split an "<h>" node.
getFirstChild()
Get the first child of a tree node.
getNextSibling()
Get the next sibling of any node.
splitArg()
Split a "<part>" node into an associative array containing: name PPNode name index String index value...
getName()
Get the name of this node.
item( $i)
Returns an item of an array-type node.
splitExt()
Split an "<ext>" node into an associative array containing name, attr, inner and close All values in ...
static factory(array $store, $index)
Construct an appropriate PPNode_Hash_* object with a class that depends on what is at the relevant st...
splitExt()
Split an "<ext>" node into an associative array containing name, attr, inner and close All values in ...
static splitRawArg(array $children)
Like splitArg() but for a raw child array.
splitArg()
Split a "<part>" node into an associative array containing:
$index
The index into $this->store which contains the descriptor of this node.
getNextSibling()
Get the next sibling, or false if there is none.
static splitRawTemplate(array $children)
Like splitTemplate() but for a raw child array.
splitHeading()
Split an "<h>" node.
splitTemplate()
Split a "<template>" or "<tplarg>" node.
static splitRawHeading(array $children)
Like splitHeading() but for a raw child array.
$store
The store array for the siblings of this node, including this node itself.
static splitRawExt(array $children)
Like splitExt() but for a raw child array.
__construct(array $store, $index)
Construct an object using the data from $store[$index].
getChildrenOfType( $name)
Get an array of the children with a given node name.
__toString()
Convert a node to XML, for debugging.
const CHILDREN
The offset of the child list within descriptors, used in some places for readability.
$rawChildren
The store array for children of this node.
getFirstChild()
Get the first child, or false if there is none.
getRawChildren()
Get the raw child array.
const NAME
The offset of the name within descriptors, used in some places for readability.
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition Parser.php:70
Differences from DOM schema:
static addLiteral(array &$accum, $text)
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.
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition deferred.txt:11
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
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
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing & $attribs
Definition hooks.txt:1984
processing should stop and the error should be shown to the user * false
Definition hooks.txt:187
There are three types of nodes:
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